diff options
| author | Bdale Garbee <bdale@gag.com> | 2012-09-12 20:01:22 -0600 |
|---|---|---|
| committer | Bdale Garbee <bdale@gag.com> | 2012-09-12 20:01:22 -0600 |
| commit | 3b612efcd1dddc6a3d59012f7ed57754b1f798c2 (patch) | |
| tree | 18d50713491ef96c5c127a309f870efb6c33f98d /altosdroid | |
| parent | e076773c1693e2a62bb828dee71c04c20dbab0a5 (diff) | |
| parent | 01eb36408d7e0e826b431fcc1d3b2deb23607e0b (diff) | |
Merge branch 'new-debian' into debian
Conflicts:
ChangeLog
debian/altos.install
debian/changelog
debian/control
debian/copyright
debian/dirs
debian/docs
debian/menu
debian/rules
src/Makefile
Diffstat (limited to 'altosdroid')
33 files changed, 2481 insertions, 0 deletions
diff --git a/altosdroid/.classpath b/altosdroid/.classpath new file mode 100644 index 00000000..0ca188f9 --- /dev/null +++ b/altosdroid/.classpath @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="src" path="gen"/> + <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> + <classpathentry kind="output" path="bin/classes"/> +</classpath> diff --git a/altosdroid/.gitignore b/altosdroid/.gitignore new file mode 100644 index 00000000..c0bb8dd4 --- /dev/null +++ b/altosdroid/.gitignore @@ -0,0 +1,3 @@ +local.properties +bin +gen diff --git a/altosdroid/.project b/altosdroid/.project new file mode 100644 index 00000000..7b56596a --- /dev/null +++ b/altosdroid/.project @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>AltosDroid</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>com.android.ide.eclipse.adt.ApkBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>com.android.ide.eclipse.adt.AndroidNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/altosdroid/.settings/org.eclipse.jdt.core.prefs b/altosdroid/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..e5d1cd30 --- /dev/null +++ b/altosdroid/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +#Wed Sep 28 19:51:24 NZDT 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/altosdroid/AndroidManifest.xml b/altosdroid/AndroidManifest.xml new file mode 100644 index 00000000..12391759 --- /dev/null +++ b/altosdroid/AndroidManifest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright © 2012 Mike Beattie <mike@ethernal.org> + * + * 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; version 2 of the License. + * + * 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., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.altusmetrum.AltosDroid" + android:versionCode="1" + android:versionName="1.0"> + <uses-sdk android:targetSdkVersion="10" android:minSdkVersion="10"/> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH" /> + + <application android:label="@string/app_name" + android:icon="@drawable/app_icon" > + <activity android:label="@string/app_name" + android:configChanges="orientation|keyboardHidden" android:name="org.altusmetrum.AltosDroid.AltosDroid"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name=".DeviceListActivity" + android:label="@string/select_device" + android:theme="@android:style/Theme.Dialog" + android:configChanges="orientation|keyboardHidden" /> + + + <service android:name=".TelemetryService" /> + + + </application> +</manifest> diff --git a/altosdroid/Makefile.am b/altosdroid/Makefile.am new file mode 100644 index 00000000..36d28ca2 --- /dev/null +++ b/altosdroid/Makefile.am @@ -0,0 +1,54 @@ +if ANDROID +all_target=bin/AltosDroid-debug.apk bin/AltosDroid-release.apk +clean_command=ant clean +else +all_target= +clean_command=echo done +endif + +SDK=$(ANDROID_SDK) + +DX=$(SDK)/platform-tools/dx +ADB=$(SDK)/platform-tools/adb +AAPT=$(SDK)/platform-tools/aapt +APKBUILDER=$(SDK)/tools/apkbuilder +ZIPALIGN=$(SDK)/tools/zipalign + +SRC_DIR=src/org/altusmetrum/AltosDroid +EXT_LIBDIR=libs +ALTOSLIB_SRCDIR=../altoslib +ALTOSLIB_JAR=AltosLib.jar + +ALTOSLIB=$(EXT_LIBDIR)/$(ALTOSLIB_JAR) + +SRC=\ + $(SRC_DIR)/AltosDroid.java \ + $(SRC_DIR)/TelemetryService.java \ + $(SRC_DIR)/TelemetryReader.java \ + $(SRC_DIR)/AltosBluetooth.java \ + $(SRC_DIR)/DeviceListActivity.java \ + $(SRC_DIR)/Dumper.java + +all: $(all_target) + +$(ALTOSLIB): $(ALTOSLIB_SRCDIR)/$(ALTOSLIB_JAR) + mkdir -p $(EXT_LIBDIR) + cd $(EXT_LIBDIR) && ln -s $(shell echo $(EXT_LIBDIR) | sed 's|[^/]\+|..|g')/$(ALTOSLIB_SRCDIR)/$(ALTOSLIB_JAR) . + +if ANDROID +install-release: bin/AltosDroid-release.apk + $(ADB) install -r bin/AltosDroid-release.apk + +install-debug: bin/AltosDroid-debug.apk + $(ADB) install -r bin/AltosDroid-debug.apk + +bin/AltosDroid-debug.apk: $(SRC) $(ALTOSLIB) + ant debug + +bin/AltosDroid-release.apk: $(SRC) $(ALTOSLIB) + ant release +endif + +clean: + $(clean_command) + diff --git a/altosdroid/build.xml b/altosdroid/build.xml new file mode 100644 index 00000000..6a89edbe --- /dev/null +++ b/altosdroid/build.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="AltosDroid" default="help"> + + <!-- The local.properties file is created and updated by the 'android' tool. + It contains the path to the SDK. It should *NOT* be checked into + Version Control Systems. --> + <property file="local.properties" /> + + <!-- The ant.properties file can be created by you. It is only edited by the + 'android' tool to add properties to it. + This is the place to change some Ant specific build properties. + Here are some properties you may want to change/update: + + source.dir + The name of the source directory. Default is 'src'. + out.dir + The name of the output directory. Default is 'bin'. + + For other overridable properties, look at the beginning of the rules + files in the SDK, at tools/ant/build.xml + + Properties related to the SDK location or the project target should + be updated using the 'android' tool with the 'update' action. + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. + + --> + <property file="ant.properties" /> + + <!-- The project.properties file is created and updated by the 'android' + tool, as well as ADT. + + This contains project specific properties such as project target, and library + dependencies. Lower level build properties are stored in ant.properties + (or in .classpath for Eclipse projects). + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. --> + <loadproperties srcFile="project.properties" /> + + <!-- quick check on sdk.dir --> + <fail + message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var" + unless="sdk.dir" + /> + + +<!-- extension targets. Uncomment the ones where you want to do custom work + in between standard targets --> +<!-- + <target name="-pre-build"> + </target> + <target name="-pre-compile"> + </target> + + /* This is typically used for code obfuscation. + Compiled code location: ${out.classes.absolute.dir} + If this is not done in place, override ${out.dex.input.absolute.dir} */ + <target name="-post-compile"> + </target> +--> + + <!-- Import the actual build file. + + To customize existing targets, there are two options: + - Customize only one target: + - copy/paste the target into this file, *before* the + <import> task. + - customize it to your needs. + - Customize the whole content of build.xml + - copy/paste the content of the rules files (minus the top node) + into this file, replacing the <import> task. + - customize to your needs. + + *********************** + ****** IMPORTANT ****** + *********************** + In all cases you must update the value of version-tag below to read 'custom' instead of an integer, + in order to avoid having your file be overridden by tools such as "android update project" + --> + <!-- version-tag: 1 --> + <import file="${sdk.dir}/tools/ant/build.xml" /> + +</project> diff --git a/altosdroid/default.properties b/altosdroid/default.properties new file mode 100644 index 00000000..66db0d15 --- /dev/null +++ b/altosdroid/default.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "build.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-10 diff --git a/altosdroid/keystore b/altosdroid/keystore Binary files differnew file mode 100644 index 00000000..00739d03 --- /dev/null +++ b/altosdroid/keystore diff --git a/altosdroid/libs/.gitignore b/altosdroid/libs/.gitignore new file mode 100644 index 00000000..b4e68f63 --- /dev/null +++ b/altosdroid/libs/.gitignore @@ -0,0 +1 @@ +AltosLib.jar diff --git a/altosdroid/local.properties.in b/altosdroid/local.properties.in new file mode 100644 index 00000000..14df0494 --- /dev/null +++ b/altosdroid/local.properties.in @@ -0,0 +1,10 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked in Version Control Systems, +# as it contains information specific to your local configuration. + +# location of the SDK. This is only used by Ant +# For customization when using a Version Control System, please read the +# header note. +sdk.dir=@ANDROID_SDK@ diff --git a/altosdroid/project.properties b/altosdroid/project.properties new file mode 100644 index 00000000..0a80e644 --- /dev/null +++ b/altosdroid/project.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=Google Inc.:Google APIs:10 diff --git a/altosdroid/res/drawable-hdpi/am_status_c.png b/altosdroid/res/drawable-hdpi/am_status_c.png Binary files differnew file mode 100644 index 00000000..d4393217 --- /dev/null +++ b/altosdroid/res/drawable-hdpi/am_status_c.png diff --git a/altosdroid/res/drawable-hdpi/am_status_g.png b/altosdroid/res/drawable-hdpi/am_status_g.png Binary files differnew file mode 100644 index 00000000..03f9dd7d --- /dev/null +++ b/altosdroid/res/drawable-hdpi/am_status_g.png diff --git a/altosdroid/res/drawable-hdpi/app_icon.png b/altosdroid/res/drawable-hdpi/app_icon.png Binary files differnew file mode 100644 index 00000000..da2f08a8 --- /dev/null +++ b/altosdroid/res/drawable-hdpi/app_icon.png diff --git a/altosdroid/res/drawable-mdpi/am_status_c.png b/altosdroid/res/drawable-mdpi/am_status_c.png Binary files differnew file mode 100644 index 00000000..30a8d29a --- /dev/null +++ b/altosdroid/res/drawable-mdpi/am_status_c.png diff --git a/altosdroid/res/drawable-mdpi/am_status_g.png b/altosdroid/res/drawable-mdpi/am_status_g.png Binary files differnew file mode 100644 index 00000000..07f7f073 --- /dev/null +++ b/altosdroid/res/drawable-mdpi/am_status_g.png diff --git a/altosdroid/res/drawable/app_icon.png b/altosdroid/res/drawable/app_icon.png Binary files differnew file mode 100644 index 00000000..b0a3c9c0 --- /dev/null +++ b/altosdroid/res/drawable/app_icon.png diff --git a/altosdroid/res/layout/altosdroid.xml b/altosdroid/res/layout/altosdroid.xml new file mode 100644 index 00000000..f185ea9f --- /dev/null +++ b/altosdroid/res/layout/altosdroid.xml @@ -0,0 +1,349 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="0" > + + <RelativeLayout + android:id="@+id/strut" + android:layout_width="10dip" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" > + + </RelativeLayout> + + <RelativeLayout + android:id="@+id/callsign_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_toLeftOf="@+id/strut" > + + <TextView + android:id="@+id/callsign_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/callsign_label" /> + + <TextView + android:id="@+id/callsign_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@id/callsign_label" + android:text="" + android:textAppearance="?android:attr/textAppearanceLarge" /> + + </RelativeLayout> + + <RelativeLayout + android:id="@+id/rssi_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toRightOf="@id/strut" + android:layout_alignParentRight="true" > + + <TextView + android:id="@+id/rssi_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/rssi_label" /> + + <TextView + android:id="@+id/rssi_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/rssi_label" + android:textAppearance="?android:attr/textAppearanceLarge" /> + </RelativeLayout> + + <RelativeLayout + android:id="@+id/serial_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/callsign_container" + android:layout_toLeftOf="@+id/strut" > + + <TextView + android:id="@+id/serial_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/serial_label" /> + + <TextView + android:id="@+id/serial_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/serial_label" + android:textAppearance="?android:attr/textAppearanceLarge" /> + </RelativeLayout> + + <RelativeLayout + android:id="@+id/flight_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/callsign_container" + android:layout_toRightOf="@+id/strut" + android:layout_alignParentRight="true" > + + <TextView + android:id="@+id/flight_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/flight_label" /> + + <TextView + android:id="@+id/flight_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/flight_label" + android:textAppearance="?android:attr/textAppearanceLarge" /> + </RelativeLayout> + + <RelativeLayout + android:id="@+id/state_container" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/serial_container" > + + <TextView + android:id="@+id/state_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/state_label" /> + + <TextView + android:id="@+id/state_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/state_label" + android:layout_centerInParent="true" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textSize="50dip" /> + + </RelativeLayout> + + <RelativeLayout + android:id="@+id/speed_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_below="@+id/state_container" + android:layout_toLeftOf="@+id/strut" > + + <TextView + android:id="@+id/speed_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/speed_label" /> + + <TextView + android:id="@+id/speed_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@id/speed_label" + android:text="" + android:textAppearance="?android:attr/textAppearanceLarge" /> + + </RelativeLayout> + + <RelativeLayout + android:id="@+id/accel_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/state_container" + android:layout_toRightOf="@+id/strut" > + + <TextView + android:id="@+id/accel_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/accel_label" /> + + <TextView + android:id="@+id/accel_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/accel_label" + android:text="" + android:textAppearance="?android:attr/textAppearanceLarge" /> + + </RelativeLayout> + + <RelativeLayout + android:id="@+id/range_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_below="@+id/speed_container" + android:layout_toLeftOf="@+id/strut" > + + <TextView + android:id="@+id/range_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/range_label" /> + + <TextView + android:id="@+id/range_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/range_label" + android:text="" + android:textAppearance="?android:attr/textAppearanceLarge" /> + + </RelativeLayout> + + <RelativeLayout + android:id="@+id/height_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@id/speed_container" + android:layout_toRightOf="@id/strut" > + + <TextView + android:id="@+id/height_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/height_label" /> + + <TextView + android:id="@+id/height_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/height_label" + android:text="" + android:textAppearance="?android:attr/textAppearanceLarge" /> + </RelativeLayout> + + <RelativeLayout + android:id="@+id/elevation_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_below="@id/range_container" + android:layout_toLeftOf="@id/strut" > + + <TextView + android:id="@+id/elevation_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/elevation_label" /> + + <TextView + android:id="@+id/elevation_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/elevation_label" + android:text="" + android:textAppearance="?android:attr/textAppearanceLarge" /> + </RelativeLayout> + + <RelativeLayout + android:id="@+id/bearing_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/range_container" + android:layout_toRightOf="@+id/strut" > + + <TextView + android:id="@+id/bearing_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/bearing_label" /> + + <TextView + android:id="@+id/bearing_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/bearing_label" + android:text="" + android:textAppearance="?android:attr/textAppearanceLarge" /> + + </RelativeLayout> + + <RelativeLayout + android:id="@+id/latitude_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/elevation_container" > + + <TextView + android:id="@+id/latitude_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/latitude_label" /> + + <TextView + android:id="@+id/latitude_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/latitude_label" + android:text="" + android:textAppearance="?android:attr/textAppearanceLarge" /> + + </RelativeLayout> + + <RelativeLayout + android:id="@+id/longitude_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/latitude_container" > + + <TextView + android:id="@+id/longitude_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/longitude_label" /> + + <TextView + android:id="@+id/longitude_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/longitude_label" + android:text="" + android:textAppearance="?android:attr/textAppearanceLarge" /> + + </RelativeLayout> + + + <TextView + android:id="@+id/text" + android:layout_width="fill_parent" + android:layout_height="0dip" + android:layout_alignParentBottom="true" + android:layout_below="@+id/longitude_container" + android:gravity="bottom" + android:scrollbars="vertical" + android:textSize="7dp" + android:typeface="monospace" /> + + </RelativeLayout> diff --git a/altosdroid/res/layout/custom_title.xml b/altosdroid/res/layout/custom_title.xml new file mode 100644 index 00000000..57eb6b4a --- /dev/null +++ b/altosdroid/res/layout/custom_title.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_vertical" + > + <TextView android:id="@+id/title_left_text" + android:layout_alignParentLeft="true" + android:ellipsize="end" + android:singleLine="true" + style="?android:attr/windowTitleStyle" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="1" + /> + <TextView android:id="@+id/title_right_text" + android:layout_alignParentRight="true" + android:ellipsize="end" + android:singleLine="true" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:textColor="#fff" + android:layout_weight="1" + /> +</RelativeLayout>
\ No newline at end of file diff --git a/altosdroid/res/layout/device_list.xml b/altosdroid/res/layout/device_list.xml new file mode 100644 index 00000000..395695f8 --- /dev/null +++ b/altosdroid/res/layout/device_list.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + > + <TextView android:id="@+id/title_paired_devices" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/title_paired_devices" + android:visibility="gone" + android:background="#666" + android:textColor="#fff" + android:paddingLeft="5dp" + /> + <ListView android:id="@+id/paired_devices" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:stackFromBottom="true" + android:layout_weight="1" + /> + <TextView android:id="@+id/title_new_devices" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/title_other_devices" + android:visibility="gone" + android:background="#666" + android:textColor="#fff" + android:paddingLeft="5dp" + /> + <ListView android:id="@+id/new_devices" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:stackFromBottom="true" + android:layout_weight="2" + /> + <Button android:id="@+id/button_scan" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/button_scan" + /> +</LinearLayout>
\ No newline at end of file diff --git a/altosdroid/res/layout/device_name.xml b/altosdroid/res/layout/device_name.xml new file mode 100644 index 00000000..8fa358c9 --- /dev/null +++ b/altosdroid/res/layout/device_name.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="18sp" + android:padding="5dp" +/>
\ No newline at end of file diff --git a/altosdroid/res/layout/main.xml b/altosdroid/res/layout/main.xml new file mode 100644 index 00000000..00ca63c8 --- /dev/null +++ b/altosdroid/res/layout/main.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + > + + <TextView + android:id="@+id/in" + android:layout_width="fill_parent" + android:layout_height="0dip" + android:layout_weight="1" + android:gravity="bottom" + android:scrollbars="vertical" + android:textSize="7dp" + android:typeface="monospace" /> + +</LinearLayout> diff --git a/altosdroid/res/layout/message.xml b/altosdroid/res/layout/message.xml new file mode 100644 index 00000000..8fa358c9 --- /dev/null +++ b/altosdroid/res/layout/message.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="18sp" + android:padding="5dp" +/>
\ No newline at end of file diff --git a/altosdroid/res/menu/option_menu.xml b/altosdroid/res/menu/option_menu.xml new file mode 100644 index 00000000..d7ba8305 --- /dev/null +++ b/altosdroid/res/menu/option_menu.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/connect_scan" + android:icon="@android:drawable/ic_menu_search" + android:title="@string/connect_device" /> + <item android:id="@+id/select_freq" + android:icon="@android:drawable/ic_menu_preferences" + android:title="@string/select_freq" /> +</menu> diff --git a/altosdroid/res/values/strings.xml b/altosdroid/res/values/strings.xml new file mode 100644 index 00000000..1b28284a --- /dev/null +++ b/altosdroid/res/values/strings.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <string name="app_name">AltosDroid</string> + + <!-- AltosDroid --> + <string name="bt_not_enabled">Bluetooth was not enabled.</string> + <string name="title_connecting">connecting…</string> + <string name="title_connected_to">connected: </string> + <string name="title_not_connected">not connected</string> + + <!-- Options Menu --> + <string name="connect_device">Connect a device</string> + <string name="select_freq">Select radio frequency</string> + + <!-- DeviceListActivity --> + <string name="scanning">scanning for devices…</string> + <string name="select_device">select a device to connect</string> + <string name="none_paired">No devices have been paired</string> + <string name="none_found">No devices found</string> + <string name="title_paired_devices">Paired Devices</string> + <string name="title_other_devices">Other Available Devices</string> + <string name="button_scan">Scan for devices</string> + + <!-- Service --> + <string name="telemetry_service_label">AltosDroid Telemetry Service</string> + <string name="telemetry_service_started">Telemetry Service Started</string> + <string name="telemetry_service_stopped">Telemetry Service Stopped</string> + + + <!-- UI fields --> + <string name="callsign_label">Callsign</string> + <string name="serial_label">Serial no.</string> + <string name="flight_label">Flight no.</string> + <string name="rssi_label">RSSI</string> + <string name="state_label">State</string> + <string name="speed_label">Speed</string> + <string name="accel_label">Acceleration</string> + <string name="range_label">Range</string> + <string name="height_label">Height</string> + <string name="elevation_label">Elevation</string> + <string name="bearing_label">Bearing</string> + <string name="latitude_label">Latitude</string> + <string name="longitude_label">Longitude</string> + +</resources> diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java new file mode 100644 index 00000000..9fcc4eba --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java @@ -0,0 +1,224 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * Copyright © 2012 Mike Beattie <mike@ethernal.org> + * + * 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; version 2 of the License. + * + * 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., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.AltosDroid; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.UUID; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSocket; +//import android.os.Bundle; +import android.os.Handler; +//import android.os.Message; +import android.util.Log; + +import org.altusmetrum.AltosLib.*; + +public class AltosBluetooth extends AltosLink { + + // Debugging + private static final String TAG = "AltosBluetooth"; + private static final boolean D = true; + + private ConnectThread connect_thread = null; + private Thread input_thread = null; + + private Handler handler; + + private BluetoothAdapter adapter; + private BluetoothDevice device; + private BluetoothSocket socket; + private InputStream input; + private OutputStream output; + + // Constructor + public AltosBluetooth(BluetoothDevice in_device, Handler in_handler) { + adapter = BluetoothAdapter.getDefaultAdapter(); + device = in_device; + handler = in_handler; + + connect_thread = new ConnectThread(device); + connect_thread.start(); + + } + + private class ConnectThread extends Thread { + private final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); + + public ConnectThread(BluetoothDevice device) { + BluetoothSocket tmp_socket = null; + + try { + tmp_socket = device.createInsecureRfcommSocketToServiceRecord(SPP_UUID); + } catch (IOException e) { + e.printStackTrace(); + } + socket = tmp_socket; + } + + public void run() { + if (D) Log.d(TAG, "ConnectThread: BEGIN"); + setName("ConnectThread"); + + // Always cancel discovery because it will slow down a connection + adapter.cancelDiscovery(); + + synchronized (AltosBluetooth.this) { + // Make a connection to the BluetoothSocket + try { + // This is a blocking call and will only return on a + // successful connection or an exception + socket.connect(); + + input = socket.getInputStream(); + output = socket.getOutputStream(); + } catch (IOException e) { + // Close the socket + try { + socket.close(); + } catch (IOException e2) { + if (D) Log.e(TAG, "ConnectThread: Failed to close() socket after failed connection"); + } + input = null; + output = null; + AltosBluetooth.this.notifyAll(); + handler.obtainMessage(TelemetryService.MSG_CONNECT_FAILED).sendToTarget(); + if (D) Log.e(TAG, "ConnectThread: Failed to establish connection"); + return; + } + + input_thread = new Thread(AltosBluetooth.this); + input_thread.start(); + + // Configure the newly connected device for telemetry + print("~\nE 0\n"); + set_monitor(false); + + // Let TelemetryService know we're connected + handler.obtainMessage(TelemetryService.MSG_CONNECTED).sendToTarget(); + + // Notify other waiting threads, now that we're connected + AltosBluetooth.this.notifyAll(); + + // Reset the ConnectThread because we're done + connect_thread = null; + + if (D) Log.d(TAG, "ConnectThread: Connect completed"); + } + } + + public void cancel() { + try { + if (socket != null) + socket.close(); + } catch (IOException e) { + if (D) Log.e(TAG, "ConnectThread: close() of connect socket failed", e); + } + } + } + + private synchronized void wait_connected() throws InterruptedException, IOException { + if (input == null) { + wait(); + if (input == null) throw new IOException(); + } + } + + private void connection_lost() { + if (D) Log.e(TAG, "Connection lost during I/O"); + handler.obtainMessage(TelemetryService.MSG_DISCONNECTED).sendToTarget(); + } + + public void print(String data) { + byte[] bytes = data.getBytes(); + if (D) Log.d(TAG, "print(): begin"); + try { + wait_connected(); + output.write(bytes); + if (D) Log.d(TAG, "print(): Wrote bytes: '" + data.replace('\n', '\\') + "'"); + } catch (IOException e) { + connection_lost(); + } catch (InterruptedException e) { + connection_lost(); + } + } + + public int getchar() { + try { + wait_connected(); + return input.read(); + } catch (IOException e) { + connection_lost(); + } catch (java.lang.InterruptedException e) { + connection_lost(); + } + return AltosLink.ERROR; + } + + public void close() { + if (D) Log.d(TAG, "close(): begin"); + synchronized(this) { + if (D) Log.d(TAG, "close(): synched"); + + if (connect_thread != null) { + if (D) Log.d(TAG, "close(): stopping connect_thread"); + connect_thread.cancel(); + connect_thread = null; + } + if (D) Log.d(TAG, "close(): Closing socket"); + try { + socket.close(); + } catch (IOException e) { + if (D) Log.e(TAG, "close(): unable to close() socket"); + } + if (input_thread != null) { + if (D) Log.d(TAG, "close(): stopping input_thread"); + try { + if (D) Log.d(TAG, "close(): input_thread.interrupt()....."); + input_thread.interrupt(); + if (D) Log.d(TAG, "close(): input_thread.join()....."); + input_thread.join(); + } catch (Exception e) {} + input_thread = null; + } + input = null; + output = null; + notifyAll(); + } + } + + + // We override this method so that we can add some debugging. Not 100% elegant, but more useful + // than debugging one char at a time above in getchar()! + public void add_reply(AltosLine line) throws InterruptedException { + if (D) Log.d(TAG, String.format("Got REPLY: %s", line.line)); + super.add_reply(line); + } + + //public void flush_output() { super.flush_output(); } + + // Stubs of required methods when extending AltosLink + public boolean can_cancel_reply() { return false; } + public boolean show_reply_timeout() { return true; } + public void hide_reply_timeout() { } + +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java new file mode 100644 index 00000000..00689684 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java @@ -0,0 +1,411 @@ +/* + * Copyright © 2012 Mike Beattie <mike@ethernal.org> + * + * 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; version 2 of the License. + * + * 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., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.AltosDroid; + +import java.lang.ref.WeakReference; + +import android.app.Activity; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Intent; +import android.content.Context; +import android.content.ComponentName; +import android.content.ServiceConnection; +import android.content.DialogInterface; +import android.os.IBinder; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.text.method.ScrollingMovementMethod; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.Window; +import android.widget.TextView; +import android.widget.Toast; +import android.app.AlertDialog; + +import org.altusmetrum.AltosLib.*; + +/** + * This is the main Activity that displays the current chat session. + */ +public class AltosDroid extends Activity { + // Debugging + private static final String TAG = "AltosDroid"; + private static final boolean D = true; + + // Message types received by our Handler + public static final int MSG_STATE_CHANGE = 1; + public static final int MSG_TELEMETRY = 2; + + // Intent request codes + private static final int REQUEST_CONNECT_DEVICE = 1; + private static final int REQUEST_ENABLE_BT = 2; + + // Layout Views + private TextView mTitle; + + // Flight state values + private TextView mCallsignView; + private TextView mRSSIView; + private TextView mSerialView; + private TextView mFlightView; + private TextView mStateView; + private TextView mSpeedView; + private TextView mAccelView; + private TextView mRangeView; + private TextView mHeightView; + private TextView mElevationView; + private TextView mBearingView; + private TextView mLatitudeView; + private TextView mLongitudeView; + + // Generic field for extras at the bottom + private TextView mTextView; + + // Service + private boolean mIsBound = false; + private Messenger mService = null; + final Messenger mMessenger = new Messenger(new IncomingHandler(this)); + + // TeleBT Config data + private AltosConfigData mConfigData = null; + // Local Bluetooth adapter + private BluetoothAdapter mBluetoothAdapter = null; + + // Text to Speech + private AltosVoice mAltosVoice = null; + + // The Handler that gets information back from the Telemetry Service + static class IncomingHandler extends Handler { + private final WeakReference<AltosDroid> mAltosDroid; + IncomingHandler(AltosDroid ad) { mAltosDroid = new WeakReference<AltosDroid>(ad); } + + @Override + public void handleMessage(Message msg) { + AltosDroid ad = mAltosDroid.get(); + switch (msg.what) { + case MSG_STATE_CHANGE: + if(D) Log.d(TAG, "MSG_STATE_CHANGE: " + msg.arg1); + switch (msg.arg1) { + case TelemetryService.STATE_CONNECTED: + ad.mConfigData = (AltosConfigData) msg.obj; + String str = String.format(" %s S/N: %d", ad.mConfigData.product, ad.mConfigData.serial); + ad.mTitle.setText(R.string.title_connected_to); + ad.mTitle.append(str); + Toast.makeText(ad.getApplicationContext(), "Connected to " + str, Toast.LENGTH_SHORT).show(); + ad.mAltosVoice.speak("Connected"); + //TEST! + ad.mTextView.setText(Dumper.dump(ad.mConfigData)); + break; + case TelemetryService.STATE_CONNECTING: + ad.mTitle.setText(R.string.title_connecting); + break; + case TelemetryService.STATE_READY: + case TelemetryService.STATE_NONE: + ad.mConfigData = null; + ad.mTitle.setText(R.string.title_not_connected); + ad.mTextView.setText(""); + break; + } + break; + case MSG_TELEMETRY: + ad.update_ui((AltosState) msg.obj); + // TEST! + ad.mTextView.setText(Dumper.dump(msg.obj)); + break; + } + } + }; + + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + mService = new Messenger(service); + try { + Message msg = Message.obtain(null, TelemetryService.MSG_REGISTER_CLIENT); + msg.replyTo = mMessenger; + mService.send(msg); + } catch (RemoteException e) { + // In this case the service has crashed before we could even do anything with it + } + } + + public void onServiceDisconnected(ComponentName className) { + // This is called when the connection with the service has been unexpectedly disconnected - process crashed. + mService = null; + } + }; + + + void doBindService() { + bindService(new Intent(this, TelemetryService.class), mConnection, Context.BIND_AUTO_CREATE); + mIsBound = true; + } + + void doUnbindService() { + if (mIsBound) { + // If we have received the service, and hence registered with it, then now is the time to unregister. + if (mService != null) { + try { + Message msg = Message.obtain(null, TelemetryService.MSG_UNREGISTER_CLIENT); + msg.replyTo = mMessenger; + mService.send(msg); + } catch (RemoteException e) { + // There is nothing special we need to do if the service has crashed. + } + } + // Detach our existing connection. + unbindService(mConnection); + mIsBound = false; + } + } + + void update_ui(AltosState state) { + mCallsignView.setText(state.data.callsign); + mRSSIView.setText(String.format("%d", state.data.rssi)); + mSerialView.setText(String.format("%d", state.data.serial)); + mFlightView.setText(String.format("%d", state.data.flight)); + mStateView.setText(state.data.state()); + double speed = state.speed; + if (!state.ascent) + speed = state.baro_speed; + mSpeedView.setText(String.format("%6.0f m/s", speed)); + mAccelView.setText(String.format("%6.0f m/s²", state.acceleration)); + mRangeView.setText(String.format("%6.0f m", state.range)); + mHeightView.setText(String.format("%6.0f m", state.height)); + mElevationView.setText(String.format("%3.0f°", state.elevation)); + if (state.from_pad != null) + mBearingView.setText(String.format("%3.0f°", state.from_pad.bearing)); + mLatitudeView.setText(pos(state.gps.lat, "N", "S")); + mLongitudeView.setText(pos(state.gps.lon, "W", "E")); + + mAltosVoice.tell(state); + } + + String pos(double p, String pos, String neg) { + String h = pos; + if (p < 0) { + h = neg; + p = -p; + } + int deg = (int) Math.floor(p); + double min = (p - Math.floor(p)) * 60.0; + return String.format("%d° %9.6f\" %s", deg, min, h); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if(D) Log.e(TAG, "+++ ON CREATE +++"); + + // Get local Bluetooth adapter + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + // If the adapter is null, then Bluetooth is not supported + if (mBluetoothAdapter == null) { + Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show(); + finish(); + return; + } + + // Set up the window layout + requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); + //setContentView(R.layout.main); + setContentView(R.layout.altosdroid); + getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title); + + // Set up the custom title + mTitle = (TextView) findViewById(R.id.title_left_text); + mTitle.setText(R.string.app_name); + mTitle = (TextView) findViewById(R.id.title_right_text); + + // Set up the temporary Text View + mTextView = (TextView) findViewById(R.id.text); + mTextView.setMovementMethod(new ScrollingMovementMethod()); + mTextView.setClickable(false); + mTextView.setLongClickable(false); + + mCallsignView = (TextView) findViewById(R.id.callsign_value); + mRSSIView = (TextView) findViewById(R.id.rssi_value); + mSerialView = (TextView) findViewById(R.id.serial_value); + mFlightView = (TextView) findViewById(R.id.flight_value); + mStateView = (TextView) findViewById(R.id.state_value); + mSpeedView = (TextView) findViewById(R.id.speed_value); + mAccelView = (TextView) findViewById(R.id.accel_value); + mRangeView = (TextView) findViewById(R.id.range_value); + mHeightView = (TextView) findViewById(R.id.height_value); + mElevationView = (TextView) findViewById(R.id.elevation_value); + mBearingView = (TextView) findViewById(R.id.bearing_value); + mLatitudeView = (TextView) findViewById(R.id.latitude_value); + mLongitudeView = (TextView) findViewById(R.id.longitude_value); + + mAltosVoice = new AltosVoice(this); + } + + @Override + public void onStart() { + super.onStart(); + if(D) Log.e(TAG, "++ ON START ++"); + + if (!mBluetoothAdapter.isEnabled()) { + Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + startActivityForResult(enableIntent, REQUEST_ENABLE_BT); + } + + // Start Telemetry Service + startService(new Intent(AltosDroid.this, TelemetryService.class)); + + doBindService(); + } + + @Override + public synchronized void onResume() { + super.onResume(); + if(D) Log.e(TAG, "+ ON RESUME +"); + } + + @Override + public synchronized void onPause() { + super.onPause(); + if(D) Log.e(TAG, "- ON PAUSE -"); + } + + @Override + public void onStop() { + super.onStop(); + if(D) Log.e(TAG, "-- ON STOP --"); + + doUnbindService(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if(D) Log.e(TAG, "--- ON DESTROY ---"); + + mAltosVoice.stop(); + } + + + + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if(D) Log.d(TAG, "onActivityResult " + resultCode); + switch (requestCode) { + case REQUEST_CONNECT_DEVICE: + // When DeviceListActivity returns with a device to connect to + if (resultCode == Activity.RESULT_OK) { + connectDevice(data); + } + break; + case REQUEST_ENABLE_BT: + // When the request to enable Bluetooth returns + if (resultCode == Activity.RESULT_OK) { + // Bluetooth is now enabled, so set up a chat session + //setupChat(); + } else { + // User did not enable Bluetooth or an error occured + Log.e(TAG, "BT not enabled"); + stopService(new Intent(AltosDroid.this, TelemetryService.class)); + Toast.makeText(this, R.string.bt_not_enabled, Toast.LENGTH_SHORT).show(); + finish(); + } + break; + } + } + + private void connectDevice(Intent data) { + // Get the device MAC address + String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS); + // Get the BLuetoothDevice object + BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); + // Attempt to connect to the device + try { + if (D) Log.d(TAG, "Connecting to " + device.getName()); + mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, device)); + } catch (RemoteException e) { + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.option_menu, menu); + return true; + } + + void setFrequency(double freq) { + try { + mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq)); + } catch (RemoteException e) { + } + } + + void setFrequency(String freq) { + try { + setFrequency (Double.parseDouble(freq.substring(11, 17))); + } catch (NumberFormatException e) { + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + Intent serverIntent = null; + switch (item.getItemId()) { + case R.id.connect_scan: + // Launch the DeviceListActivity to see devices and do scan + serverIntent = new Intent(this, DeviceListActivity.class); + startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE); + return true; + case R.id.select_freq: + // Set the TBT radio frequency + + final String[] frequencies = { + "Channel 0 (434.550MHz)", + "Channel 1 (434.650MHz)", + "Channel 2 (434.750MHz)", + "Channel 3 (434.850MHz)", + "Channel 4 (434.950MHz)", + "Channel 5 (435.050MHz)", + "Channel 6 (435.150MHz)", + "Channel 7 (435.250MHz)", + "Channel 8 (435.350MHz)", + "Channel 9 (435.450MHz)" + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Pick a frequency"); + builder.setItems(frequencies, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + setFrequency(frequencies[item]); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + return true; + } + return false; + } + +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java new file mode 100644 index 00000000..3382d551 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java @@ -0,0 +1,203 @@ +/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ * Copyright © 2012 Mike Beattie <mike@ethernal.org>
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.AltosDroid;
+
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TextToSpeech.OnInitListener;
+
+import org.altusmetrum.AltosLib.*;
+
+public class AltosVoice {
+
+ private TextToSpeech tts = null;
+ private boolean tts_enabled = false;
+
+ private IdleThread idle_thread = null;
+
+ private AltosState old_state = null;
+
+ public AltosVoice(AltosDroid a) {
+
+ tts = new TextToSpeech(a, new OnInitListener() {
+ public void onInit(int status) {
+ if (status == TextToSpeech.SUCCESS) tts_enabled = true;
+ if (tts_enabled) {
+ speak("AltosDroid ready");
+ idle_thread = new IdleThread();
+ }
+ }
+ });
+
+ }
+
+ public void speak(String s) {
+ if (!tts_enabled) return;
+ tts.speak(s, TextToSpeech.QUEUE_ADD, null);
+ }
+
+ public void stop() {
+ if (tts != null) tts.shutdown();
+ if (idle_thread != null) {
+ idle_thread.interrupt();
+ idle_thread = null;
+ }
+ }
+
+ public void tell(AltosState state) {
+ if (!tts_enabled) return;
+
+ boolean spoke = false;
+ if (old_state == null || old_state.state != state.state) {
+ speak(state.data.state());
+ if ((old_state == null || old_state.state <= AltosLib.ao_flight_boost) &&
+ state.state > AltosLib.ao_flight_boost) {
+ speak(String.format("max speed: %d meters per second.", (int) (state.max_speed + 0.5)));
+ spoke = true;
+ } else if ((old_state == null || old_state.state < AltosLib.ao_flight_drogue) &&
+ state.state >= AltosLib.ao_flight_drogue) {
+ speak(String.format("max height: %d meters.", (int) (state.max_height + 0.5)));
+ spoke = true;
+ }
+ }
+ if (old_state == null || old_state.gps_ready != state.gps_ready) {
+ if (state.gps_ready) {
+ speak("GPS ready");
+ spoke = true;
+ } else if (old_state != null) {
+ speak("GPS lost");
+ spoke = true;
+ }
+ }
+ old_state = state;
+ idle_thread.notice(state, spoke);
+ }
+
+
+ class IdleThread extends Thread {
+ boolean started;
+ private AltosState state;
+ int reported_landing;
+ int report_interval;
+ long report_time;
+
+ public synchronized void report(boolean last) {
+ if (state == null)
+ return;
+
+ /* reset the landing count once we hear about a new flight */
+ if (state.state < AltosLib.ao_flight_drogue)
+ reported_landing = 0;
+
+ /* Shut up once the rocket is on the ground */
+ if (reported_landing > 2) {
+ return;
+ }
+
+ /* If the rocket isn't on the pad, then report height */
+ if (AltosLib.ao_flight_drogue <= state.state &&
+ state.state < AltosLib.ao_flight_landed &&
+ state.range >= 0)
+ {
+ speak(String.format("Height %d, bearing %s %d, elevation %d, range %d.\n",
+ (int) (state.height + 0.5),
+ state.from_pad.bearing_words(
+ AltosGreatCircle.BEARING_VOICE),
+ (int) (state.from_pad.bearing + 0.5),
+ (int) (state.elevation + 0.5),
+ (int) (state.range + 0.5)));
+ } else if (state.state > AltosLib.ao_flight_pad) {
+ speak(String.format("%d meters", (int) (state.height + 0.5)));
+ } else {
+ reported_landing = 0;
+ }
+
+ /* If the rocket is coming down, check to see if it has landed;
+ * either we've got a landed report or we haven't heard from it in
+ * a long time
+ */
+ if (state.state >= AltosLib.ao_flight_drogue &&
+ (last ||
+ System.currentTimeMillis() - state.report_time >= 15000 ||
+ state.state == AltosLib.ao_flight_landed))
+ {
+ if (Math.abs(state.baro_speed) < 20 && state.height < 100)
+ speak("rocket landed safely");
+ else
+ speak("rocket may have crashed");
+ if (state.from_pad != null)
+ speak(String.format("Bearing %d degrees, range %d meters.",
+ (int) (state.from_pad.bearing + 0.5),
+ (int) (state.from_pad.distance + 0.5)));
+ ++reported_landing;
+ }
+ }
+
+ long now () {
+ return System.currentTimeMillis();
+ }
+
+ void set_report_time() {
+ report_time = now() + report_interval;
+ }
+
+ public void run () {
+ try {
+ for (;;) {
+ set_report_time();
+ for (;;) {
+ synchronized (this) {
+ long sleep_time = report_time - now();
+ if (sleep_time <= 0)
+ break;
+ wait(sleep_time);
+ }
+ }
+ report(false);
+ }
+ } catch (InterruptedException ie) {
+ }
+ }
+
+ public synchronized void notice(AltosState new_state, boolean spoken) {
+ AltosState old_state = state;
+ state = new_state;
+ if (!started && state.state > AltosLib.ao_flight_pad) {
+ started = true;
+ start();
+ }
+
+ if (state.state < AltosLib.ao_flight_drogue)
+ report_interval = 10000;
+ else
+ report_interval = 20000;
+ if (old_state != null && old_state.state != state.state) {
+ report_time = now();
+ this.notify();
+ } else if (spoken)
+ set_report_time();
+ }
+
+ public IdleThread() {
+ state = null;
+ reported_landing = 0;
+ report_interval = 10000;
+ }
+ }
+
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java new file mode 100644 index 00000000..7b9cbde7 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.altusmetrum.AltosDroid; + +import java.util.Set; +import org.altusmetrum.AltosDroid.R; + +import android.app.Activity; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.AdapterView.OnItemClickListener; + +/** + * This Activity appears as a dialog. It lists any paired devices and + * devices detected in the area after discovery. When a device is chosen + * by the user, the MAC address of the device is sent back to the parent + * Activity in the result Intent. + */ +public class DeviceListActivity extends Activity { + // Debugging + private static final String TAG = "DeviceListActivity"; + private static final boolean D = true; + + // Return Intent extra + public static String EXTRA_DEVICE_ADDRESS = "device_address"; + + // Member fields + private BluetoothAdapter mBtAdapter; + private ArrayAdapter<String> mPairedDevicesArrayAdapter; + private ArrayAdapter<String> mNewDevicesArrayAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Setup the window + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + setContentView(R.layout.device_list); + + // Set result CANCELED incase the user backs out + setResult(Activity.RESULT_CANCELED); + + // Initialize the button to perform device discovery + Button scanButton = (Button) findViewById(R.id.button_scan); + scanButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + doDiscovery(); + v.setVisibility(View.GONE); + } + }); + + // Initialize array adapters. One for already paired devices and + // one for newly discovered devices + mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name); + mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name); + + // Find and set up the ListView for paired devices + ListView pairedListView = (ListView) findViewById(R.id.paired_devices); + pairedListView.setAdapter(mPairedDevicesArrayAdapter); + pairedListView.setOnItemClickListener(mDeviceClickListener); + + // Find and set up the ListView for newly discovered devices + ListView newDevicesListView = (ListView) findViewById(R.id.new_devices); + newDevicesListView.setAdapter(mNewDevicesArrayAdapter); + newDevicesListView.setOnItemClickListener(mDeviceClickListener); + + // Register for broadcasts when a device is discovered + IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); + this.registerReceiver(mReceiver, filter); + + // Register for broadcasts when discovery has finished + filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); + this.registerReceiver(mReceiver, filter); + + // Get the local Bluetooth adapter + mBtAdapter = BluetoothAdapter.getDefaultAdapter(); + + // Get a set of currently paired devices + Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices(); + + // If there are paired devices, add each one to the ArrayAdapter + if (pairedDevices.size() > 0) { + findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE); + for (BluetoothDevice device : pairedDevices) { + mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); + } + } else { + String noDevices = getResources().getText(R.string.none_paired).toString(); + mPairedDevicesArrayAdapter.add(noDevices); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + // Make sure we're not doing discovery anymore + if (mBtAdapter != null) { + mBtAdapter.cancelDiscovery(); + } + + // Unregister broadcast listeners + this.unregisterReceiver(mReceiver); + } + + /** + * Start device discover with the BluetoothAdapter + */ + private void doDiscovery() { + if (D) Log.d(TAG, "doDiscovery()"); + + // Indicate scanning in the title + setProgressBarIndeterminateVisibility(true); + setTitle(R.string.scanning); + + // Turn on sub-title for new devices + findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE); + + // If we're already discovering, stop it + if (mBtAdapter.isDiscovering()) { + mBtAdapter.cancelDiscovery(); + } + + // Request discover from BluetoothAdapter + mBtAdapter.startDiscovery(); + } + + // The on-click listener for all devices in the ListViews + private OnItemClickListener mDeviceClickListener = new OnItemClickListener() { + public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) { + // Cancel discovery because it's costly and we're about to connect + mBtAdapter.cancelDiscovery(); + + // Get the device MAC address, which is the last 17 chars in the View + String info = ((TextView) v).getText().toString(); + String address = info.substring(info.length() - 17); + + // Create the result Intent and include the MAC address + Intent intent = new Intent(); + intent.putExtra(EXTRA_DEVICE_ADDRESS, address); + + // Set result and finish this Activity + setResult(Activity.RESULT_OK, intent); + finish(); + } + }; + + // The BroadcastReceiver that listens for discovered devices and + // changes the title when discovery is finished + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + // When discovery finds a device + if (BluetoothDevice.ACTION_FOUND.equals(action)) { + // Get the BluetoothDevice object from the Intent + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + // If it's already paired, skip it, because it's been listed already + if (device.getBondState() != BluetoothDevice.BOND_BONDED) { + mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); + } + // When discovery is finished, change the Activity title + } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { + setProgressBarIndeterminateVisibility(false); + setTitle(R.string.select_device); + if (mNewDevicesArrayAdapter.getCount() == 0) { + String noDevices = getResources().getText(R.string.none_found).toString(); + mNewDevicesArrayAdapter.add(noDevices); + } + } + } + }; + +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/Dumper.java b/altosdroid/src/org/altusmetrum/AltosDroid/Dumper.java new file mode 100644 index 00000000..17e4cf5b --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/Dumper.java @@ -0,0 +1,183 @@ +package org.altusmetrum.AltosDroid;
+
+ import java.lang.reflect.Array;
+ import java.lang.reflect.Field;
+ import java.util.HashMap;
+
+ public class Dumper {
+ private static Dumper instance = new Dumper();
+
+ protected static Dumper getInstance() {
+ return instance;
+ }
+
+ class DumpContext {
+ int maxDepth = 0;
+ int maxArrayElements = 0;
+ int callCount = 0;
+ HashMap<String, String> ignoreList = new HashMap<String, String>();
+ HashMap<Object, Integer> visited = new HashMap<Object, Integer>();
+ }
+
+ public static String dump(Object o) {
+ return dump(o, 0, 0, null);
+ }
+
+ public static String dump(Object o, int maxDepth, int maxArrayElements, String[] ignoreList) {
+ DumpContext ctx = Dumper.getInstance().new DumpContext();
+ ctx.maxDepth = maxDepth;
+ ctx.maxArrayElements = maxArrayElements;
+
+ if (ignoreList != null) {
+ for (int i = 0; i < Array.getLength(ignoreList); i++) {
+ int colonIdx = ignoreList[i].indexOf(':');
+ if (colonIdx == -1)
+ ignoreList[i] = ignoreList[i] + ":";
+ ctx.ignoreList.put(ignoreList[i], ignoreList[i]);
+ }
+ }
+
+ return dump(o, ctx);
+ }
+
+ protected static String dump(Object o, DumpContext ctx) {
+ if (o == null) {
+ return "<null>";
+ }
+
+ ctx.callCount++;
+ StringBuffer tabs = new StringBuffer();
+ for (int k = 0; k < ctx.callCount; k++) {
+ tabs.append("\t");
+ }
+ StringBuffer buffer = new StringBuffer();
+ @SuppressWarnings("rawtypes")
+ Class oClass = o.getClass();
+
+ String oSimpleName = getSimpleNameWithoutArrayQualifier(oClass);
+
+ if (ctx.ignoreList.get(oSimpleName + ":") != null)
+ return "<Ignored>";
+
+ if (oClass.isArray()) {
+ buffer.append("\n");
+ buffer.append(tabs.toString().substring(1));
+ buffer.append("[\n");
+ int rowCount = ctx.maxArrayElements == 0 ? Array.getLength(o) : Math.min(ctx.maxArrayElements, Array.getLength(o));
+ for (int i = 0; i < rowCount; i++) {
+ buffer.append(tabs.toString());
+ try {
+ Object value = Array.get(o, i);
+ buffer.append(dumpValue(value, ctx));
+ } catch (Exception e) {
+ buffer.append(e.getMessage());
+ }
+ if (i < Array.getLength(o) - 1)
+ buffer.append(",");
+ buffer.append("\n");
+ }
+ if (rowCount < Array.getLength(o)) {
+ buffer.append(tabs.toString());
+ buffer.append(Array.getLength(o) - rowCount + " more array elements...");
+ buffer.append("\n");
+ }
+ buffer.append(tabs.toString().substring(1));
+ buffer.append("]");
+ } else {
+ buffer.append("\n");
+ buffer.append(tabs.toString().substring(1));
+ buffer.append("{\n");
+ buffer.append(tabs.toString());
+ buffer.append("hashCode: " + o.hashCode());
+ buffer.append("\n");
+ while (oClass != null && oClass != Object.class) {
+ Field[] fields = oClass.getDeclaredFields();
+
+ if (ctx.ignoreList.get(oClass.getSimpleName()) == null) {
+ if (oClass != o.getClass()) {
+ buffer.append(tabs.toString().substring(1));
+ buffer.append(" Inherited from superclass " + oSimpleName + ":\n");
+ }
+
+ for (int i = 0; i < fields.length; i++) {
+
+ String fSimpleName = getSimpleNameWithoutArrayQualifier(fields[i].getType());
+ String fName = fields[i].getName();
+
+ fields[i].setAccessible(true);
+ buffer.append(tabs.toString());
+ buffer.append(fName + "(" + fSimpleName + ")");
+ buffer.append("=");
+
+ if (ctx.ignoreList.get(":" + fName) == null &&
+ ctx.ignoreList.get(fSimpleName + ":" + fName) == null &&
+ ctx.ignoreList.get(fSimpleName + ":") == null) {
+
+ try {
+ Object value = fields[i].get(o);
+ buffer.append(dumpValue(value, ctx));
+ } catch (Exception e) {
+ buffer.append(e.getMessage());
+ }
+ buffer.append("\n");
+ } else {
+ buffer.append("<Ignored>");
+ buffer.append("\n");
+ }
+ }
+ oClass = oClass.getSuperclass();
+ oSimpleName = oClass.getSimpleName();
+ } else {
+ oClass = null;
+ oSimpleName = "";
+ }
+ }
+ buffer.append(tabs.toString().substring(1));
+ buffer.append("}");
+ }
+ ctx.callCount--;
+ return buffer.toString();
+ }
+
+ protected static String dumpValue(Object value, DumpContext ctx) {
+ if (value == null) {
+ return "<null>";
+ }
+ if (value.getClass().isPrimitive() ||
+ value.getClass() == java.lang.Short.class ||
+ value.getClass() == java.lang.Long.class ||
+ value.getClass() == java.lang.String.class ||
+ value.getClass() == java.lang.Integer.class ||
+ value.getClass() == java.lang.Float.class ||
+ value.getClass() == java.lang.Byte.class ||
+ value.getClass() == java.lang.Character.class ||
+ value.getClass() == java.lang.Double.class ||
+ value.getClass() == java.lang.Boolean.class) {
+
+ return value.toString();
+
+ } else {
+
+ Integer visitedIndex = ctx.visited.get(value);
+ if (visitedIndex == null) {
+ ctx.visited.put(value, ctx.callCount);
+ if (ctx.maxDepth == 0 || ctx.callCount < ctx.maxDepth) {
+ return dump(value, ctx);
+ } else {
+ return "<Reached max recursion depth>";
+ }
+ } else {
+ return "<Previously visited - see hashCode " + value.hashCode() + ">";
+ }
+ }
+ }
+
+
+ private static String getSimpleNameWithoutArrayQualifier(@SuppressWarnings("rawtypes") Class clazz) {
+ String simpleName = clazz.getSimpleName();
+ int indexOfBracket = simpleName.indexOf('[');
+ if (indexOfBracket != -1)
+ return simpleName.substring(0, indexOfBracket);
+ return simpleName;
+ }
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java new file mode 100644 index 00000000..66e9c6bd --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java @@ -0,0 +1,94 @@ +/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ * Copyright © 2012 Mike Beattie <mike@ethernal.org>
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+
+package org.altusmetrum.AltosDroid;
+
+import java.text.*;
+import java.io.*;
+import java.util.concurrent.*;
+import android.util.Log;
+import android.os.Handler;
+
+import org.altusmetrum.AltosLib.*;
+
+
+public class TelemetryReader extends Thread {
+
+ private static final String TAG = "TelemetryReader";
+
+ int crc_errors;
+
+ Handler handler;
+
+ AltosLink link;
+ AltosRecord previous;
+
+ LinkedBlockingQueue<AltosLine> telem;
+
+ public AltosRecord read() throws ParseException, AltosCRCException, InterruptedException, IOException {
+ AltosLine l = telem.take();
+ if (l.line == null)
+ throw new IOException("IO error");
+ AltosRecord next = AltosTelemetry.parse(l.line, previous);
+ previous = next;
+ return next;
+ }
+
+ public void close() {
+ previous = null;
+ link.remove_monitor(telem);
+ link = null;
+ telem.clear();
+ telem = null;
+ }
+
+ public void run() {
+ AltosState state = null;
+
+ try {
+ for (;;) {
+ try {
+ AltosRecord record = read();
+ if (record == null)
+ break;
+ state = new AltosState(record, state);
+
+ handler.obtainMessage(TelemetryService.MSG_TELEMETRY, state).sendToTarget();
+ } catch (ParseException pp) {
+ Log.e(TAG, String.format("Parse error: %d \"%s\"", pp.getErrorOffset(), pp.getMessage()));
+ } catch (AltosCRCException ce) {
+ ++crc_errors;
+ }
+ }
+ } catch (InterruptedException ee) {
+ } catch (IOException ie) {
+ } finally {
+ close();
+ }
+ }
+
+ public TelemetryReader (AltosLink in_link, Handler in_handler) {
+ link = in_link;
+ handler = in_handler;
+
+ previous = null;
+ telem = new LinkedBlockingQueue<AltosLine>();
+ link.add_monitor(telem);
+ }
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java new file mode 100644 index 00000000..393fd2f6 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java @@ -0,0 +1,295 @@ +/* + * Copyright © 2012 Mike Beattie <mike@ethernal.org> + * + * 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; version 2 of the License. + * + * 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., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.AltosDroid; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.concurrent.TimeoutException; +import java.util.Timer; +import java.util.TimerTask; + +import android.app.Notification; +//import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.bluetooth.BluetoothDevice; +import android.content.Intent; +//import android.os.Bundle; +import android.os.IBinder; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; +import android.widget.Toast; + +import org.altusmetrum.AltosLib.*; + +public class TelemetryService extends Service { + + private static final String TAG = "TelemetryService"; + private static final boolean D = true; + + static final int MSG_REGISTER_CLIENT = 1; + static final int MSG_UNREGISTER_CLIENT = 2; + static final int MSG_CONNECT = 3; + static final int MSG_CONNECTED = 4; + static final int MSG_CONNECT_FAILED = 5; + static final int MSG_DISCONNECTED = 6; + static final int MSG_TELEMETRY = 7; + static final int MSG_SETFREQUENCY = 8; + + public static final int STATE_NONE = 0; + public static final int STATE_READY = 1; + public static final int STATE_CONNECTING = 2; + public static final int STATE_CONNECTED = 3; + + // Unique Identification Number for the Notification. + // We use it on Notification start, and to cancel it. + private int NOTIFICATION = R.string.telemetry_service_label; + //private NotificationManager mNM; + + // Timer - we wake up every now and then to decide if the service should stop + private Timer timer = new Timer(); + + ArrayList<Messenger> mClients = new ArrayList<Messenger>(); // Keeps track of all current registered clients. + final Handler mHandler = new IncomingHandler(this); + final Messenger mMessenger = new Messenger(mHandler); // Target we publish for clients to send messages to IncomingHandler. + + // Name of the connected device + private BluetoothDevice device = null; + private AltosBluetooth mAltosBluetooth = null; + private AltosConfigData mConfigData = null; + private TelemetryReader mTelemetryReader = null; + + // internally track state of bluetooth connection + private int state = STATE_NONE; + + // Handler of incoming messages from clients. + static class IncomingHandler extends Handler { + private final WeakReference<TelemetryService> service; + IncomingHandler(TelemetryService s) { service = new WeakReference<TelemetryService>(s); } + + @Override + public void handleMessage(Message msg) { + TelemetryService s = service.get(); + switch (msg.what) { + case MSG_REGISTER_CLIENT: + s.mClients.add(msg.replyTo); + try { + // Now we try to send the freshly connected UI any relavant information about what + // we're talking to - Basically state and Config Data. + msg.replyTo.send(Message.obtain(null, AltosDroid.MSG_STATE_CHANGE, s.state, -1, s.mConfigData)); + } catch (RemoteException e) { + s.mClients.remove(msg.replyTo); + } + if (D) Log.d(TAG, "Client bound to service"); + break; + case MSG_UNREGISTER_CLIENT: + s.mClients.remove(msg.replyTo); + if (D) Log.d(TAG, "Client unbound from service"); + break; + case MSG_CONNECT: + if (D) Log.d(TAG, "Connect command received"); + s.device = (BluetoothDevice) msg.obj; + s.startAltosBluetooth(); + break; + case MSG_CONNECTED: + if (D) Log.d(TAG, "Connected to device"); + s.connected(); + break; + case MSG_CONNECT_FAILED: + if (D) Log.d(TAG, "Connection failed... retrying"); + s.startAltosBluetooth(); + break; + case MSG_DISCONNECTED: + // Only do the following if we haven't been shutdown elsewhere.. + if (s.device != null) { + if (D) Log.d(TAG, "Disconnected from " + s.device.getName()); + s.stopAltosBluetooth(); + } + break; + case MSG_TELEMETRY: + s.sendMessageToClients(Message.obtain(null, AltosDroid.MSG_TELEMETRY, msg.obj)); + break; + case MSG_SETFREQUENCY: + if (s.state == STATE_CONNECTED) { + try { + s.mAltosBluetooth.set_radio_frequency((Double) msg.obj); + } catch (InterruptedException e) { + } catch (TimeoutException e) { + } + } + break; + default: + super.handleMessage(msg); + } + } + } + + private void sendMessageToClients(Message m) { + for (int i=mClients.size()-1; i>=0; i--) { + try { + mClients.get(i).send(m); + } catch (RemoteException e) { + mClients.remove(i); + } + } + } + + private void stopAltosBluetooth() { + if (D) Log.d(TAG, "stopAltosBluetooth(): begin"); + setState(STATE_READY); + if (mTelemetryReader != null) { + if (D) Log.d(TAG, "stopAltosBluetooth(): stopping TelemetryReader"); + mTelemetryReader.interrupt(); + try { + mTelemetryReader.join(); + } catch (InterruptedException e) { + } + mTelemetryReader = null; + } + if (mAltosBluetooth != null) { + if (D) Log.d(TAG, "stopAltosBluetooth(): stopping AltosBluetooth"); + mAltosBluetooth.close(); + mAltosBluetooth = null; + } + device = null; + mConfigData = null; + } + + private void startAltosBluetooth() { + if (mAltosBluetooth == null) { + if (D) Log.d(TAG, String.format("startAltosBluetooth(): Connecting to %s (%s)", device.getName(), device.getAddress())); + mAltosBluetooth = new AltosBluetooth(device, mHandler); + setState(STATE_CONNECTING); + } else { + // This is a bit of a hack - if it appears we're still connected, we treat this as a restart. + // So, to give a suitable delay to teardown/bringup, we just schedule a resend of a message + // to ourselves in a few seconds time that will ultimately call this method again. + // ... then we tear down the existing connection. + // We do it this way around so that we don't lose a reference to the device when this method + // is called on reception of MSG_CONNECT_FAILED in the handler above. + mHandler.sendMessageDelayed(Message.obtain(null, MSG_CONNECT, device), 3000); + stopAltosBluetooth(); + } + } + + private synchronized void setState(int s) { + if (D) Log.d(TAG, "setState(): " + state + " -> " + s); + state = s; + + // This shouldn't be required - mConfigData should be null for any non-connected + // state, but to be safe and to reduce message size + AltosConfigData acd = (state == STATE_CONNECTED) ? mConfigData : null; + + sendMessageToClients(Message.obtain(null, AltosDroid.MSG_STATE_CHANGE, state, -1, acd)); + } + + private void connected() { + try { + mConfigData = mAltosBluetooth.config_data(); + } catch (InterruptedException e) { + } catch (TimeoutException e) { + // If this timed out, then we really want to retry it, but + // probably safer to just retry the connection from scratch. + mHandler.obtainMessage(MSG_CONNECT_FAILED).sendToTarget(); + return; + } + + setState(STATE_CONNECTED); + + mTelemetryReader = new TelemetryReader(mAltosBluetooth, mHandler); + mTelemetryReader.start(); + } + + + private void onTimerTick() { + if (D) Log.d(TAG, "Timer wakeup"); + try { + if (mClients.size() <= 0 && state != STATE_CONNECTED) { + stopSelf(); + } + } catch (Throwable t) { + Log.e(TAG, "Timer failed: ", t); + } + } + + + @Override + public void onCreate() { + // Create a reference to the NotificationManager so that we can update our notifcation text later + //mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + + setState(STATE_READY); + + // Start our timer - first event in 10 seconds, then every 10 seconds after that. + timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 10000L, 10000L); + + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.i("TelemetryService", "Received start id " + startId + ": " + intent); + + CharSequence text = getText(R.string.telemetry_service_started); + + // Create notification to be displayed while the service runs + Notification notification = new Notification(R.drawable.am_status_c, text, 0); + + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + new Intent(this, AltosDroid.class), 0); + + // Set the info for the views that show in the notification panel. + notification.setLatestEventInfo(this, getText(R.string.telemetry_service_label), text, contentIntent); + + // Set the notification to be in the "Ongoing" section. + notification.flags |= Notification.FLAG_ONGOING_EVENT; + + // Move us into the foreground. + startForeground(NOTIFICATION, notification); + + // We want this service to continue running until it is explicitly + // stopped, so return sticky. + return START_STICKY; + } + + @Override + public void onDestroy() { + + // Stop the bluetooth Comms threads + stopAltosBluetooth(); + + // Demote us from the foreground, and cancel the persistent notification. + stopForeground(true); + + // Stop our timer + if (timer != null) {timer.cancel();} + + // Tell the user we stopped. + Toast.makeText(this, R.string.telemetry_service_stopped, Toast.LENGTH_SHORT).show(); + } + + @Override + public IBinder onBind(Intent intent) { + return mMessenger.getBinder(); + } + + +} |
