diff options
| author | Bdale Garbee <bdale@gag.com> | 2015-07-16 13:36:59 -0600 | 
|---|---|---|
| committer | Bdale Garbee <bdale@gag.com> | 2015-07-16 13:36:59 -0600 | 
| commit | f4f0f044df1251d64d44bf62d25b488fd04a05c8 (patch) | |
| tree | 12efee1447a67d8cf37b6f447c011708264b8266 /altosdroid | |
| parent | b25690062ac04a588ad4d11740597c20e516eb1a (diff) | |
| parent | 570daace9caf7647a09c53d5c75593cc4c98b93b (diff) | |
Merge branch 'branch-1.6' into debian
Diffstat (limited to 'altosdroid')
52 files changed, 5437 insertions, 2567 deletions
diff --git a/altosdroid/AndroidManifest.xml b/altosdroid/AndroidManifest.xml.in index 19e5a6dc..24035796 100644 --- a/altosdroid/AndroidManifest.xml +++ b/altosdroid/AndroidManifest.xml.in @@ -17,9 +17,9 @@  -->  <manifest xmlns:android="http://schemas.android.com/apk/res/android"            package="org.altusmetrum.AltosDroid" -          android:versionCode="6" -          android:versionName="1.5"> -    <uses-sdk android:targetSdkVersion="10" android:minSdkVersion="10"/> +          android:versionCode="@ANDROID_VERSION@" +          android:versionName="@VERSION@"> +    <uses-sdk android:targetSdkVersion="12" android:minSdkVersion="12"/>      <!-- Google Maps -->      <uses-feature android:glEsVersion="0x00020000" android:required="true"/> @@ -38,23 +38,50 @@                  android:protectionLevel="signature"/>      <uses-permission android:name="org.altusmetrum.AltosDroid.permission.MAPS_RECEIVE"/> +    <!-- Permissions needed to access USB OTG --> +    <uses-feature android:name="android.hardware.usb.host" />      <application android:label="@string/app_name"                   android:icon="@drawable/app_icon" -                 android:allowBackup="true" > +                 android:allowBackup="true" +		 android:theme="@style/CustomTheme">          <activity android:name="org.altusmetrum.AltosDroid.AltosDroid"                    android:label="@string/app_name" -                  android:configChanges="orientation|keyboardHidden" > +                  android:configChanges="orientation|keyboardHidden" +		  android:launchMode="singleTop">              <intent-filter>                  <action android:name="android.intent.action.MAIN" />                  <category android:name="android.intent.category.LAUNCHER" />              </intent-filter>          </activity> + +	<activity android:name="org.altusmetrum.AltosDroid.AltosDroid" +                  android:configChanges="orientation|keyboardHidden" +		  android:launchMode="singleTop"> +	  <intent-filter> +	    <action +		android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/> +	  </intent-filter> +	  <meta-data +	      android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" +	      android:resource="@xml/device_filter" /> +	</activity> +          <activity android:name=".DeviceListActivity"                    android:label="@string/select_device"                    android:theme="@android:style/Theme.Dialog"                    android:configChanges="orientation|keyboardHidden" /> +        <activity android:name=".PreloadMapActivity" +                  android:label="@string/preload_maps" +                  android:theme="@android:style/Theme.Dialog" +                  android:configChanges="orientation|keyboardHidden" /> + +        <activity android:name=".MapTypeActivity" +                  android:label="@string/map_type" +                  android:theme="@android:style/Theme.Dialog" +                  android:configChanges="orientation|keyboardHidden" /> +          <service android:name=".TelemetryService" />          <meta-data android:name="com.google.android.maps.v2.API_KEY" diff --git a/altosdroid/Makefile.am b/altosdroid/Makefile.am index 361de13c..26e14ee7 100644 --- a/altosdroid/Makefile.am +++ b/altosdroid/Makefile.am @@ -14,11 +14,16 @@ DX=$(SDK)/platform-tools/dx  ADB=$(SDK)/platform-tools/adb  AAPT=$(SDK)/platform-tools/aapt  APKBUILDER=$(SDK)/tools/apkbuilder -ZIPALIGN=$(SDK)/tools/zipalign +ZIPALIGN_A=$(SDK)/tools/zipalign +ZIPALIGN_B=$(SDK)/build-tools/*/zipalign  JAVA_SRC_DIR=src/org/altusmetrum/AltosDroid  EXT_LIBDIR=libs  DRAWABLE_DIR=res/drawable +LAYOUT_DIR=res/layout +MENU_DIR=res/menu +VALUES_DIR=res/values +XML_DIR=res/xml  ALTOSLIB_SRCDIR=../altoslib  ALTOSLIB_JAR=altoslib_$(ALTOSLIB_VERSION).jar @@ -39,19 +44,25 @@ DRAWABLES=\      $(DRAWABLE_DIR)/greenled.png \      $(DRAWABLE_DIR)/grayled.png -SRC=$(JAVA_SRC) $(DRAWABLES) +LAYOUTS=$(LAYOUT_DIR)/*.xml +MENUS=$(MENU_DIR)/*.xml +VALUES=$(VALUES_DIR)/*.xml +XMLS=$(XML_DIR)/*.xml + +RES=$(LAYOUTS) $(MENUS) $(VALUES) $(XMLS) + +SRC=$(JAVA_SRC) $(DRAWABLES) $(RES)  all: $(all_target)  .NOTPARALLEL: -$(EXT_LIBDIR): +$(ALTOSLIB): $(ALTOSLIB_SRCDIR)/$(ALTOSLIB_JAR)  	mkdir -p $(EXT_LIBDIR) - -$(ALTOSLIB): $(EXT_LIBDIR) $(ALTOSLIB_SRCDIR)/$(ALTOSLIB_JAR)  	cd $(EXT_LIBDIR) && ln -sf $(shell echo $(EXT_LIBDIR) | sed 's|[^/]\+|..|g')/$(ALTOSLIB_SRCDIR)/$(ALTOSLIB_JAR) . -$(SUPPORT_V4): $(EXT_LIBDIR) $(SUPPORT_V4_SRCDIR)/$(SUPPORT_V4_JAR) +$(SUPPORT_V4): $(SUPPORT_V4_SRCDIR)/$(SUPPORT_V4_JAR) +	mkdir -p $(EXT_LIBDIR)  	cd $(EXT_LIBDIR) && ln -sf $(SUPPORT_V4_SRCDIR)/$(SUPPORT_V4_JAR) .  $(GOOGLE_PLAY_SERVICES_LIB): $(GOOGLE_PLAY_SERVICES_LIB_SRCDIR)/$(GOOGLE_PLAY_SERVICES_LIB) @@ -83,9 +94,15 @@ bin/AltosDroid-release.apk: bin/AltosDroid-release-unsigned.apk  	   -storepass:file ~/altusmetrumllc/google-play-passphrase \  	   -signedjar bin/AltosDroid-release-signed.apk \  	   bin/AltosDroid-release-unsigned.apk AltosDroid -	$(ZIPALIGN) -f 4 \ -	   bin/AltosDroid-release-signed.apk \ -	   bin/AltosDroid-release.apk +	if [ -f $(ZIPALIGN_A) ]; then \ +		$(ZIPALIGN_A) -f 4 \ +		   bin/AltosDroid-release-signed.apk \ +		   bin/AltosDroid-release.apk; \ +	else \ +		$(ZIPALIGN_B) -f 4 \ +		   bin/AltosDroid-release-signed.apk \ +		   bin/AltosDroid-release.apk; \ +	fi  release: bin/AltosDroid-release.apk diff --git a/altosdroid/Notebook b/altosdroid/Notebook index 6a246df7..73b5ed27 100644 --- a/altosdroid/Notebook +++ b/altosdroid/Notebook @@ -15,17 +15,9 @@ Desired AltosDroid feature list   *) Monitor-idle mode - *) Frequency scanning + *) Online maps comes up tracking object at 0,0 - *) Select satellite imaging mode - - *) TeleBT battery voltage - - *) Deal with long bluetooth list. Currently, a list longer than -    the screen makes it impossible to use entries off the bottom. - - *) Pickle/unpickle state instead of reloading entire history from -    file. Current restart time is lengthy. + *) Have names for each serial number, default to callsign  Completed features @@ -47,3 +39,70 @@ Completed features  	Done + *) Select satellite imaging mode + +	Done + + *) Deal with long bluetooth list. Currently, a list longer than +    the screen makes it impossible to use entries off the bottom. + +	Done + + *) Pickle/unpickle state instead of reloading entire history from +    file. Current restart time is lengthy. + +	Done + + *) Offline maps + +	Done + + *) Multi-tracker management + +	Done + + *) Provide units for age field, turn red if old + +	Done + + *) TeleBT battery voltage + +	Done + + *) Evaluate performance issues + +	Done. Offline maps were duplicating tabs at every redisplay. + + *) Merge offline/online maps into single tab with mode + +	Done. + + *) Auto select tracker after long delay + +	Done. + + *) Select tracker by clicking map + +	Done. + + *) Convert to four tab design: + +	Done. + + *) Make voice responses depend on selected tab + +	Done. + + *) Monitor TeleMega igniters + +	Done. Visible only in Pad tab + + *) Make it harder to switch trackers in map view. Too easy to touch +    the screen and switch on accident. + +	Done. A menu pops up with trackers within a small radius of +	the touch point, letting you cancel if that wasn't your intent. + + *) Make sure it keeps talking with the screen blanked + +	Done. Don't shut down voice when stopping UI. diff --git a/altosdroid/default.properties b/altosdroid/default.properties index 66db0d15..3ac25234 100644 --- a/altosdroid/default.properties +++ b/altosdroid/default.properties @@ -8,4 +8,4 @@  # project structure.  # Project target. -target=android-10 +target=android-12 diff --git a/altosdroid/project.properties b/altosdroid/project.properties index 96b9551c..d178f98a 100644 --- a/altosdroid/project.properties +++ b/altosdroid/project.properties @@ -8,5 +8,5 @@  # project structure.  # Project target. -target=android-10 +target=android-12  android.library.reference.1=google-play-services_lib/ diff --git a/altosdroid/res/drawable-hdpi/ic_maps_indicator_current_position.png b/altosdroid/res/drawable-hdpi/ic_maps_indicator_current_position.png Binary files differnew file mode 100644 index 00000000..bc9160df --- /dev/null +++ b/altosdroid/res/drawable-hdpi/ic_maps_indicator_current_position.png diff --git a/altosdroid/res/drawable-mdpi/ic_maps_indicator_current_position.png b/altosdroid/res/drawable-mdpi/ic_maps_indicator_current_position.png Binary files differnew file mode 100644 index 00000000..4e427d89 --- /dev/null +++ b/altosdroid/res/drawable-mdpi/ic_maps_indicator_current_position.png diff --git a/altosdroid/res/layout/device_list.xml b/altosdroid/res/layout/device_list.xml index 93d65517..bf295e4c 100644 --- a/altosdroid/res/layout/device_list.xml +++ b/altosdroid/res/layout/device_list.xml @@ -32,6 +32,13 @@          android:textColor="#fff"          android:paddingLeft="5dp"      /> +    <ListView android:id="@+id/new_devices" +        android:layout_width="match_parent" +        android:layout_height="wrap_content" +        android:layout_weight="1" +	android:fadeScrollbars="false" +	android:scrollbars="vertical" +    />      <TextView android:id="@+id/title_paired_devices"          android:layout_width="match_parent"          android:layout_height="wrap_content" @@ -44,13 +51,8 @@      <ListView android:id="@+id/paired_devices"          android:layout_width="match_parent"          android:layout_height="wrap_content" -        android:stackFromBottom="true"          android:layout_weight="1" -    /> -    <ListView android:id="@+id/new_devices" -        android:layout_width="match_parent" -        android:layout_height="wrap_content" -        android:stackFromBottom="true" -        android:layout_weight="2" +	android:fadeScrollbars="false" +	android:scrollbars="vertical"      />  </LinearLayout> diff --git a/altosdroid/res/layout/map_preload.xml b/altosdroid/res/layout/map_preload.xml new file mode 100644 index 00000000..dc613bf2 --- /dev/null +++ b/altosdroid/res/layout/map_preload.xml @@ -0,0 +1,129 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +  Copyright © 2015 Keith Packard <keithp@keithp.com> + +  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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +    android:orientation="vertical" +    android:layout_width="match_parent" +    android:layout_height="match_parent" +    > +  <ScrollView android:layout_width="fill_parent" +	      android:layout_height="wrap_content"> +    <LinearLayout android:layout_width="wrap_content" +		  android:layout_height="wrap_content" +                  android:orientation="vertical"> +      <TextView android:id="@+id/preload_site_label" +		android:layout_width="fill_parent" +		android:layout_height="wrap_content" +		android:text="@string/preload_site_label" +		/> +      <Spinner android:id="@+id/preload_site_list" +	       android:layout_width="fill_parent" +	       android:layout_height="wrap_content" +	       android:prompt="@string/preload_site_label" +	       android:spinnerMode="dropdown" +	       /> +      <TextView android:id="@+id/preload_latitude_label" +		android:layout_width="fill_parent" +		android:layout_height="wrap_content" +		android:text="@string/preload_latitude_label" +		/> +      <EditText android:id="@+id/preload_latitude" +		android:layout_width="fill_parent" +		android:layout_height="wrap_content" +		android:hint="@string/preload_latitude_label" +		android:inputType="number"/> +      <TextView android:id="@+id/preload_longitude_label" +		android:layout_width="fill_parent" +		android:layout_height="wrap_content" +		android:text="@string/preload_longitude_label" +		/> +      <EditText android:id="@+id/preload_longitude" +		android:layout_width="fill_parent" +		android:layout_height="wrap_content" +		android:hint="@string/preload_longitude_label" +		android:inputType="number"/> +      <TextView android:id="@+id/preload_types" +		android:layout_width="fill_parent" +		android:layout_height="wrap_content" +		android:text="@string/preload_types" +		/> +      <CheckBox android:id="@+id/preload_hybrid" +		android:layout_width="fill_parent" +		android:layout_height="wrap_content" +		android:text="@string/preload_hybrid" +		/> +      <CheckBox android:id="@+id/preload_satellite" +		android:layout_width="fill_parent" +		android:layout_height="wrap_content" +		android:text="@string/preload_satellite" +		/> +      <CheckBox android:id="@+id/preload_roadmap" +		android:layout_width="fill_parent" +		android:layout_height="wrap_content" +		android:text="@string/preload_roadmap" +		/> +      <CheckBox android:id="@+id/preload_terrain" +		android:layout_width="fill_parent" +		android:layout_height="wrap_content" +		android:text="@string/preload_terrain" +		/> +      <TextView android:id="@+id/preload_min_zoom_label" +		android:layout_width="fill_parent" +		android:layout_height="wrap_content" +		android:text="@string/preload_min_zoom" +		/> +      <Spinner android:id="@+id/preload_min_zoom" +	       android:layout_width="fill_parent" +	       android:layout_height="wrap_content" +	       android:prompt="@string/preload_min_zoom" +	       android:spinnerMode="dropdown" +	       /> +      <TextView android:id="@+id/preload_max_zoom_label" +		android:layout_width="fill_parent" +		android:layout_height="wrap_content" +		android:text="@string/preload_max_zoom" +		/> +      <Spinner android:id="@+id/preload_max_zoom" +	       android:layout_width="fill_parent" +	       android:layout_height="wrap_content" +	       android:prompt="@string/preload_max_zoom" +	       android:spinnerMode="dropdown" +	       /> +      <TextView android:id="@+id/preload_radius_label" +		android:layout_width="fill_parent" +		android:layout_height="wrap_content" +		android:text="@string/preload_radius" +		/> +      <Spinner android:id="@+id/preload_radius" +	       android:layout_width="fill_parent" +	       android:layout_height="wrap_content" +	       android:prompt="@string/preload_radius" +	       android:spinnerMode="dropdown" +	       /> +      <Button android:id="@+id/preload_load" +              android:layout_width="match_parent" +              android:layout_height="wrap_content" +              android:text="@string/preload_load" +	      /> +      <ProgressBar android:id="@+id/preload_progress" +		   android:layout_width="match_parent" +		   android:layout_height="wrap_content" +		   style="@android:style/Widget.ProgressBar.Horizontal" +		   /> +    </LinearLayout> +  </ScrollView> +</LinearLayout> diff --git a/altosdroid/res/layout/map_type.xml b/altosdroid/res/layout/map_type.xml new file mode 100644 index 00000000..610e6bbf --- /dev/null +++ b/altosdroid/res/layout/map_type.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +  Copyright © 2015 Keith Packard <keithp@keithp.com> + +  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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +    android:orientation="vertical" +    android:layout_width="match_parent" +    android:layout_height="match_parent" +    > +  <Button android:id="@+id/map_type_hybrid" +	   android:layout_width="fill_parent" +	   android:layout_height="wrap_content" +	   android:text="@string/preload_hybrid" +	   android:onClick="selectType" +	   /> +  <Button android:id="@+id/map_type_satellite" +	   android:layout_width="fill_parent" +	   android:layout_height="wrap_content" +	   android:text="@string/preload_satellite" +	   android:onClick="selectType" +	   /> +  <Button android:id="@+id/map_type_roadmap" +	   android:layout_width="fill_parent" +	   android:layout_height="wrap_content" +	   android:text="@string/preload_roadmap" +	   android:onClick="selectType" +	   /> +  <Button android:id="@+id/map_type_terrain" +	   android:layout_width="fill_parent" +	   android:layout_height="wrap_content" +	   android:text="@string/preload_terrain" +	   android:onClick="selectType" +	   /> +</LinearLayout> diff --git a/altosdroid/res/layout/tab_ascent.xml b/altosdroid/res/layout/tab_ascent.xml deleted file mode 100644 index b21ec426..00000000 --- a/altosdroid/res/layout/tab_ascent.xml +++ /dev/null @@ -1,299 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -     Copyright © 2013 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. ---> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" -	android:layout_width="fill_parent" -	android:layout_height="wrap_content" -	android:orientation="vertical" > - -	<LinearLayout -		xmlns:android="http://schemas.android.com/apk/res/android" -		android:layout_width="fill_parent" -		android:layout_height="wrap_content" -		android:layout_weight="0" -		android:baselineAligned="true" -		android:orientation="horizontal" -		android:paddingTop="5dp" > - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" > - -			<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/textAppearanceSmall" /> -		</RelativeLayout> - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" > - -			<TextView -				android:id="@+id/max_height_label" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:text="@string/max_height_label" /> - -			<TextView -				android:id="@+id/max_height_value" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:layout_alignParentRight="true" -				android:layout_below="@id/max_height_label" -				android:text="" -				android:textAppearance="?android:attr/textAppearanceSmall" /> -		</RelativeLayout> -	</LinearLayout> - -	<LinearLayout -		xmlns:android="http://schemas.android.com/apk/res/android" -		android:layout_width="fill_parent" -		android:layout_height="wrap_content" -		android:layout_weight="0" -		android:baselineAligned="true" -		android:orientation="horizontal" -		android:paddingTop="5dp" > - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" > - -			<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/textAppearanceSmall" /> -		</RelativeLayout> - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" > - -			<TextView -				android:id="@+id/max_speed_label" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:text="@string/max_speed_label" /> - -			<TextView -				android:id="@+id/max_speed_value" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:layout_alignParentRight="true" -				android:layout_below="@id/max_speed_label" -				android:text="" -				android:textAppearance="?android:attr/textAppearanceSmall" /> -		</RelativeLayout> -	</LinearLayout> - -	<LinearLayout -		xmlns:android="http://schemas.android.com/apk/res/android" -		android:layout_width="fill_parent" -		android:layout_height="wrap_content" -		android:layout_weight="0" -		android:baselineAligned="true" -		android:orientation="horizontal" -		android:paddingTop="5dp" > - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" > - -			<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/textAppearanceSmall" /> -		</RelativeLayout> - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" > - -			<TextView -				android:id="@+id/max_accel_label" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:text="@string/max_accel_label" /> - -			<TextView -				android:id="@+id/max_accel_value" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:layout_alignParentRight="true" -				android:layout_below="@id/max_accel_label" -				android:text="" -				android:textAppearance="?android:attr/textAppearanceSmall" /> -		</RelativeLayout> -	</LinearLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<TextView -			android:id="@+id/lat_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:text="@string/latitude_label" /> - -		<TextView -			android:id="@+id/lat_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/lat_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<TextView -			android:id="@+id/lon_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:text="@string/longitude_label" /> - -		<TextView -			android:id="@+id/lon_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/lon_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<ImageView -			android:id="@+id/apogee_redled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:contentDescription="@string/apogee_voltage_label" -			android:src="@drawable/grayled" /> - -		<ImageView -			android:id="@+id/apogee_greenled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/apogee_redled" -			android:contentDescription="@string/apogee_voltage_label" -			android:paddingRight="5dp" -			android:src="@drawable/grayled" /> - -		<TextView -			android:id="@+id/apogee_voltage_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/apogee_greenled" -			android:text="@string/apogee_voltage_label" /> - -		<TextView -			android:id="@+id/apogee_voltage_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/apogee_voltage_label" -			android:layout_toRightOf="@id/apogee_greenled" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<ImageView -			android:id="@+id/main_redled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:contentDescription="@string/main_voltage_label" -			android:src="@drawable/grayled" /> - -		<ImageView -			android:id="@+id/main_greenled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/main_redled" -			android:contentDescription="@string/main_voltage_label" -			android:paddingRight="5dp" -			android:src="@drawable/grayled" /> - -		<TextView -			android:id="@+id/main_voltage_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/main_greenled" -			android:text="@string/main_voltage_label" /> - -		<TextView -			android:id="@+id/main_voltage_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/main_voltage_label" -			android:layout_toRightOf="@id/main_greenled" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -</LinearLayout>
\ No newline at end of file diff --git a/altosdroid/res/layout/tab_descent.xml b/altosdroid/res/layout/tab_descent.xml deleted file mode 100644 index 9e1fc820..00000000 --- a/altosdroid/res/layout/tab_descent.xml +++ /dev/null @@ -1,339 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -     Copyright © 2013 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. ---> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" -	android:layout_width="fill_parent" -	android:layout_height="wrap_content" -	android:orientation="vertical" > - -	<LinearLayout -		xmlns:android="http://schemas.android.com/apk/res/android" -		android:layout_width="fill_parent" -		android:layout_height="wrap_content" -		android:layout_weight="0" -		android:baselineAligned="true" -		android:orientation="horizontal" > - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" > - -			<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/textAppearanceSmall" /> -		</RelativeLayout> - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" > - -			<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/textAppearanceSmall" /> -		</RelativeLayout> - -	</LinearLayout> - -	<LinearLayout -		xmlns:android="http://schemas.android.com/apk/res/android" -		android:layout_width="fill_parent" -		android:layout_height="wrap_content" -		android:layout_weight="0" -		android:baselineAligned="true" -		android:orientation="horizontal" -		android:paddingTop="5dp" > - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" > - -			<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/textAppearanceSmall" /> -		</RelativeLayout> - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" > - -			<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/textAppearanceSmall" /> -		</RelativeLayout> - -	</LinearLayout> - -	<LinearLayout -		xmlns:android="http://schemas.android.com/apk/res/android" -		android:layout_width="fill_parent" -		android:layout_height="wrap_content" -		android:layout_weight="0" -		android:baselineAligned="true" -		android:orientation="horizontal" -		android:paddingTop="5dp" > - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" > - -			<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/textAppearanceSmall" /> -		</RelativeLayout> - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" > - -			<TextView -				android:id="@+id/compass_label" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:text="" /> - -			<TextView -				android:id="@+id/compass_value" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:layout_alignParentRight="true" -				android:layout_below="@id/compass_label" -				android:text="" -				android:textAppearance="?android:attr/textAppearanceSmall" /> -		</RelativeLayout> - -	</LinearLayout> - -	<LinearLayout -		xmlns:android="http://schemas.android.com/apk/res/android" -		android:layout_width="fill_parent" -		android:layout_height="wrap_content" -		android:layout_weight="0" -		android:baselineAligned="true" -		android:orientation="horizontal" -		android:paddingTop="5dp" > - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" > - -			<TextView -				android:id="@+id/distance_label" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:text="@string/gnd_distance_label" /> - -			<TextView -				android:id="@+id/distance_value" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:layout_alignParentRight="true" -				android:layout_below="@id/distance_label" -				android:text="" -				android:textAppearance="?android:attr/textAppearanceSmall" /> -		</RelativeLayout> - -		<TextView -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" > -		</TextView> - -	</LinearLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<TextView -			android:id="@+id/lat_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:text="@string/latitude_label" /> - -		<TextView -			android:id="@+id/lat_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/lat_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<TextView -			android:id="@+id/lon_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:text="@string/longitude_label" /> - -		<TextView -			android:id="@+id/lon_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/lon_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<ImageView -			android:id="@+id/apogee_redled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:contentDescription="@string/apogee_voltage_label" -			android:src="@drawable/grayled" /> - -		<ImageView -			android:id="@+id/apogee_greenled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/apogee_redled" -			android:contentDescription="@string/apogee_voltage_label" -			android:paddingRight="5dp" -			android:src="@drawable/grayled" /> - -		<TextView -			android:id="@+id/apogee_voltage_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/apogee_greenled" -			android:text="@string/apogee_voltage_label" /> - -		<TextView -			android:id="@+id/apogee_voltage_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/apogee_voltage_label" -			android:layout_toRightOf="@id/apogee_greenled" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<ImageView -			android:id="@+id/main_redled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:contentDescription="@string/main_voltage_label" -			android:src="@drawable/grayled" /> - -		<ImageView -			android:id="@+id/main_greenled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/main_redled" -			android:contentDescription="@string/main_voltage_label" -			android:paddingRight="5dp" -			android:src="@drawable/grayled" /> - -		<TextView -			android:id="@+id/main_voltage_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/main_greenled" -			android:text="@string/main_voltage_label" /> - -		<TextView -			android:id="@+id/main_voltage_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/main_voltage_label" -			android:layout_toRightOf="@id/main_greenled" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -</LinearLayout> diff --git a/altosdroid/res/layout/tab_flight.xml b/altosdroid/res/layout/tab_flight.xml new file mode 100644 index 00000000..85c171b2 --- /dev/null +++ b/altosdroid/res/layout/tab_flight.xml @@ -0,0 +1,402 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +     Copyright © 2013 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. +--> +<LinearLayout +    xmlns:android="http://schemas.android.com/apk/res/android" +    android:layout_width="fill_parent" +    android:layout_height="wrap_content" +    android:orientation="vertical" > + +  <LinearLayout +      android:layout_weight="0" +      android:layout_width="fill_parent" +      android:layout_height="wrap_content" +      android:orientation="vertical" > + +    <LinearLayout +	xmlns:android="http://schemas.android.com/apk/res/android" +	android:layout_width="fill_parent" +	android:layout_height="wrap_content" +	android:layout_weight="0" +	android:baselineAligned="true" +	android:orientation="horizontal" > + +      <RelativeLayout +	  android:layout_width="0dp" +	  android:layout_height="wrap_content" +	  android:layout_weight="1" > + +	<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/textAppearanceSmall" /> +      </RelativeLayout> + +      <RelativeLayout +	  android:layout_width="0dp" +	  android:layout_height="wrap_content" +	  android:layout_weight="1" > + +	<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/textAppearanceSmall" /> +      </RelativeLayout> + +    </LinearLayout> + +    <LinearLayout +	xmlns:android="http://schemas.android.com/apk/res/android" +	android:layout_width="fill_parent" +	android:layout_height="wrap_content" +	android:layout_weight="0" +	android:baselineAligned="true" +	android:orientation="horizontal" > + +      <RelativeLayout +	  android:layout_width="0dp" +	  android:layout_height="wrap_content" +	  android:layout_weight="1" > + +	<TextView +	    android:id="@+id/max_speed_label" +	    android:layout_width="wrap_content" +	    android:layout_height="wrap_content" +	    android:text="@string/max_speed_label" /> + +	<TextView +	    android:id="@+id/max_speed_value" +	    android:layout_width="wrap_content" +	    android:layout_height="wrap_content" +	    android:layout_alignParentRight="true" +	    android:layout_below="@id/max_speed_label" +	    android:text="" +	    android:textAppearance="?android:attr/textAppearanceSmall" /> +      </RelativeLayout> + +      <RelativeLayout +	  android:layout_width="0dp" +	  android:layout_height="wrap_content" +	  android:layout_weight="1" > + +	<TextView +	    android:id="@+id/max_height_label" +	    android:layout_width="wrap_content" +	    android:layout_height="wrap_content" +	    android:text="@string/max_height_label" /> + +	<TextView +	    android:id="@+id/max_height_value" +	    android:layout_width="wrap_content" +	    android:layout_height="wrap_content" +	    android:layout_alignParentRight="true" +	    android:layout_below="@id/max_height_label" +	    android:text="" +	    android:textAppearance="?android:attr/textAppearanceSmall" /> +      </RelativeLayout> + +    </LinearLayout> + +    <LinearLayout +	xmlns:android="http://schemas.android.com/apk/res/android" +	android:layout_width="fill_parent" +	android:layout_height="wrap_content" +	android:layout_weight="0" +	android:baselineAligned="true" +	android:orientation="horizontal" +	android:paddingTop="5dp" > + +      <RelativeLayout +	  android:layout_width="0dp" +	  android:layout_height="wrap_content" +	  android:layout_weight="1" > + +	<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/textAppearanceSmall" /> +      </RelativeLayout> + +      <RelativeLayout +	  android:layout_width="0dp" +	  android:layout_height="wrap_content" +	  android:layout_weight="1" > + +	<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/textAppearanceSmall" /> +      </RelativeLayout> + +    </LinearLayout> + +    <LinearLayout +	xmlns:android="http://schemas.android.com/apk/res/android" +	android:layout_width="fill_parent" +	android:layout_height="wrap_content" +	android:layout_weight="0" +	android:baselineAligned="true" +	android:orientation="horizontal" +	android:paddingTop="5dp" > + +      <RelativeLayout +	  android:layout_width="0dp" +	  android:layout_height="wrap_content" +	  android:layout_weight="1" > + +	<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/textAppearanceSmall" /> +      </RelativeLayout> + +      <RelativeLayout +	  android:layout_width="0dp" +	  android:layout_height="wrap_content" +	  android:layout_weight="1" > + +	<TextView +	    android:id="@+id/compass_label" +	    android:layout_width="wrap_content" +	    android:layout_height="wrap_content" +	    android:text="" /> + +	<TextView +	    android:id="@+id/compass_value" +	    android:layout_width="wrap_content" +	    android:layout_height="wrap_content" +	    android:layout_alignParentRight="true" +	    android:layout_below="@id/compass_label" +	    android:text="" +	    android:textAppearance="?android:attr/textAppearanceSmall" /> +      </RelativeLayout> + +    </LinearLayout> + +    <LinearLayout +	xmlns:android="http://schemas.android.com/apk/res/android" +	android:layout_width="fill_parent" +	android:layout_height="wrap_content" +	android:layout_weight="0" +	android:baselineAligned="true" +	android:orientation="horizontal" +	android:paddingTop="5dp" > + +      <RelativeLayout +	  android:layout_width="0dp" +	  android:layout_height="wrap_content" +	  android:layout_weight="1" > + +	<TextView +	    android:id="@+id/distance_label" +	    android:layout_width="wrap_content" +	    android:layout_height="wrap_content" +	    android:text="@string/gnd_distance_label" /> + +	<TextView +	    android:id="@+id/distance_value" +	    android:layout_width="wrap_content" +	    android:layout_height="wrap_content" +	    android:layout_alignParentRight="true" +	    android:layout_below="@id/distance_label" +	    android:text="" +	    android:textAppearance="?android:attr/textAppearanceSmall" /> +      </RelativeLayout> + +      <TextView +	  android:layout_width="0dp" +	  android:layout_height="wrap_content" +	  android:layout_weight="1" > +      </TextView> + +    </LinearLayout> + +    <RelativeLayout +	android:layout_width="wrap_content" +	android:layout_height="wrap_content" +	android:paddingTop="5dp" > + +      <TextView +	  android:id="@+id/lat_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/latitude_label" /> + +      <TextView +	  android:id="@+id/lat_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_alignParentRight="true" +	  android:layout_below="@id/lat_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:layout_width="wrap_content" +	android:layout_height="wrap_content" +	android:paddingTop="5dp" > + +      <TextView +	  android:id="@+id/lon_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/longitude_label" /> + +      <TextView +	  android:id="@+id/lon_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_alignParentRight="true" +	  android:layout_below="@id/lon_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:id="@+id/apogee_view" +	android:visibility="gone" +	android:layout_gravity="fill" +	android:layout_weight="1" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content" +	android:paddingTop="5dp" > + +      <ImageView +	  android:id="@+id/apogee_redled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/apogee_voltage_label" +	  android:src="@drawable/grayled" /> + +      <ImageView +	  android:id="@+id/apogee_greenled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_toRightOf="@id/apogee_redled" +	  android:contentDescription="@string/apogee_voltage_label" +	  android:paddingRight="5dp" +	  android:src="@drawable/grayled" /> + +      <TextView +	  android:id="@+id/apogee_voltage_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_toRightOf="@id/apogee_greenled" +	  android:text="@string/apogee_voltage_label" /> + +      <TextView +	  android:id="@+id/apogee_voltage_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_alignParentRight="true" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:id="@+id/main_view" +	android:visibility="gone" +	android:layout_gravity="fill" +	android:layout_weight="1" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content" +	android:paddingTop="5dp" > + +      <ImageView +	  android:id="@+id/main_redled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/main_voltage_label" +	  android:src="@drawable/grayled" /> + +      <ImageView +	  android:id="@+id/main_greenled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_toRightOf="@id/main_redled" +	  android:contentDescription="@string/main_voltage_label" +	  android:paddingRight="5dp" +	  android:src="@drawable/grayled" /> + +      <TextView +	  android:id="@+id/main_voltage_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_toRightOf="@id/main_greenled" +	  android:text="@string/main_voltage_label" /> + +      <TextView +	  android:id="@+id/main_voltage_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_alignParentRight="true" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> +  </LinearLayout> +</LinearLayout> diff --git a/altosdroid/res/layout/tab_landed.xml b/altosdroid/res/layout/tab_landed.xml deleted file mode 100644 index f27baa9e..00000000 --- a/altosdroid/res/layout/tab_landed.xml +++ /dev/null @@ -1,211 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -     Copyright © 2013 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. ---> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" -	android:layout_width="fill_parent" -	android:layout_height="wrap_content" -	android:orientation="vertical" > - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" > - -		<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/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<TextView -			android:id="@+id/distance_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:text="@string/distance_label" /> - -		<TextView -			android:id="@+id/distance_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@+id/distance_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<TextView -			android:id="@+id/target_lat_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:text="@string/target_latitude_label" /> - -		<TextView -			android:id="@+id/target_lat_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/target_lat_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<TextView -			android:id="@+id/target_lon_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:text="@string/target_longitude_label" /> - -		<TextView -			android:id="@+id/target_lon_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/target_lon_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<TextView -			android:id="@+id/receiver_lat_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:text="@string/receiver_latitude_label" /> - -		<TextView -			android:id="@+id/receiver_lat_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/receiver_lat_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<TextView -			android:id="@+id/receiver_lon_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:text="@string/receiver_longitude_label" /> - -		<TextView -			android:id="@+id/receiver_lon_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/receiver_lon_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<TextView -			android:id="@+id/max_height_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:text="@string/max_height_label" /> - -		<TextView -			android:id="@+id/max_height_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/max_height_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<TextView -			android:id="@+id/max_speed_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:text="@string/max_speed_label" /> - -		<TextView -			android:id="@+id/max_speed_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/max_speed_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<TextView -			android:id="@+id/max_accel_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:text="@string/max_accel_label" /> - -		<TextView -			android:id="@+id/max_accel_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_alignParentRight="true" -			android:layout_below="@id/max_accel_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -</LinearLayout>
\ No newline at end of file diff --git a/altosdroid/res/layout/tab_layout.xml b/altosdroid/res/layout/tab_layout.xml new file mode 100644 index 00000000..2c21c648 --- /dev/null +++ b/altosdroid/res/layout/tab_layout.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +    android:id="@+id/customTabLayout" +    android:layout_width="match_parent" +    android:layout_height="match_parent"> +    <TextView +        android:id="@+id/tabLabel" +        android:layout_height="wrap_content" +        android:layout_width="match_parent" +        android:textSize="13dp" +        android:textColor="#ffffff" +	android:gravity="center_horizontal" +	android:background="#808080" +        android:layout_centerVertical="true" +        android:layout_centerHorizontal="true" /> +</RelativeLayout> diff --git a/altosdroid/res/layout/tab_map.xml b/altosdroid/res/layout/tab_map.xml index f611ae48..952abd49 100644 --- a/altosdroid/res/layout/tab_map.xml +++ b/altosdroid/res/layout/tab_map.xml @@ -20,169 +20,182 @@  	android:layout_height="match_parent"  	android:orientation="vertical" > -	<LinearLayout -	    android:id="@+id/map" -		android:orientation="horizontal" -		android:layout_width="fill_parent" -		android:layout_height="0dp" -		android:layout_weight="1"> - -	</LinearLayout> - -	<LinearLayout -		xmlns:android="http://schemas.android.com/apk/res/android" -		android:layout_width="fill_parent" -		android:layout_height="wrap_content" -		android:baselineAligned="true" -		android:orientation="horizontal" > - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" -			android:paddingTop="5dp" > - -			<TextView -				android:id="@+id/distance_label" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:paddingRight="4dp" -				android:text="@string/distance_label" /> - -			<TextView -				android:id="@+id/distance_value" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:layout_toRightOf="@+id/distance_label" -				android:text="" -				android:textAppearance="?android:attr/textAppearanceSmall" /> -		</RelativeLayout> - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" -			android:paddingTop="5dp" > - -			<TextView -				android:id="@+id/bearing_label" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:paddingRight="4dp" -				android:text="@string/bearing_label" /> - -			<TextView -				android:id="@+id/bearing_value" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:layout_toRightOf="@+id/bearing_label" -				android:text="" -				android:textAppearance="?android:attr/textAppearanceSmall" /> -		</RelativeLayout> - -	</LinearLayout> - -	<LinearLayout -		xmlns:android="http://schemas.android.com/apk/res/android" -		android:layout_width="fill_parent" -		android:layout_height="wrap_content" -		android:baselineAligned="true" -		android:orientation="horizontal" > - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" -			android:paddingTop="5dp" > - -			<TextView -				android:id="@+id/target_lat_label" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:paddingRight="4dp" -				android:text="@string/target_latitude_label" /> - -			<TextView -				android:id="@+id/target_lat_value" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:layout_toRightOf="@id/target_lat_label" -				android:text="" -				android:textAppearance="?android:attr/textAppearanceSmall" /> -		</RelativeLayout> - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" -			android:paddingTop="5dp" > - -			<TextView -				android:id="@+id/target_lon_label" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:paddingRight="4dp" -				android:text="@string/target_longitude_label" /> - -			<TextView -				android:id="@+id/target_lon_value" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:layout_toRightOf="@id/target_lon_label" -				android:text="" -				android:textAppearance="?android:attr/textAppearanceSmall" /> -		</RelativeLayout> -	</LinearLayout> - -	<LinearLayout -		xmlns:android="http://schemas.android.com/apk/res/android" -		android:layout_width="fill_parent" -		android:layout_height="wrap_content" -		android:baselineAligned="true" -		android:orientation="horizontal" > - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" -			android:paddingTop="5dp" > - -			<TextView -				android:id="@+id/receiver_lat_label" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:paddingRight="4dp" -				android:text="@string/receiver_latitude_label" /> - -			<TextView -				android:id="@+id/receiver_lat_value" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:layout_toRightOf="@id/receiver_lat_label" -				android:text="" -				android:textAppearance="?android:attr/textAppearanceSmall" /> -		</RelativeLayout> - -		<RelativeLayout -			android:layout_width="0dp" -			android:layout_height="wrap_content" -			android:layout_weight="1" -			android:paddingTop="5dp" > - -			<TextView -				android:id="@+id/receiver_lon_label" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:paddingRight="4dp" -				android:text="@string/receiver_longitude_label" /> - -			<TextView -				android:id="@+id/receiver_lon_value" -				android:layout_width="wrap_content" -				android:layout_height="wrap_content" -				android:layout_toRightOf="@id/receiver_lon_label" -				android:text="" -				android:textAppearance="?android:attr/textAppearanceSmall" /> -		</RelativeLayout> -	</LinearLayout> -</LinearLayout>
\ No newline at end of file +  <FrameLayout +      android:layout_width="fill_parent" +      android:layout_height="wrap_content" +      android:baselineAligned="true" +      android:orientation="horizontal" +      android:layout_weight="1"> + +    <LinearLayout +	android:id="@+id/map_online" +	android:orientation="horizontal" +	android:layout_width="fill_parent" +	android:layout_height="fill_parent" +	android:layout_weight="1"> +    </LinearLayout> + +    <org.altusmetrum.AltosDroid.AltosMapOffline +	android:id="@+id/map_offline" +	android:layout_width="fill_parent" +	android:layout_height="wrap_content" +	android:layout_weight="1"/> +  </FrameLayout> +   +  <LinearLayout +      xmlns:android="http://schemas.android.com/apk/res/android" +      android:layout_width="fill_parent" +      android:layout_height="wrap_content" +      android:baselineAligned="true" +      android:orientation="horizontal" > + +    <RelativeLayout +	android:layout_width="0dp" +	android:layout_height="wrap_content" +	android:layout_weight="1" +	android:paddingTop="5dp" > + +      <TextView +	  android:id="@+id/distance_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:paddingRight="4dp" +	  android:text="@string/distance_label" /> + +      <TextView +	  android:id="@+id/distance_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_toRightOf="@id/distance_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:layout_width="0dp" +	android:layout_height="wrap_content" +	android:layout_weight="1" +	android:paddingTop="5dp" > + +      <TextView +	  android:id="@+id/bearing_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:paddingRight="4dp" +	  android:text="@string/bearing_label" /> + +      <TextView +	  android:id="@+id/bearing_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_toRightOf="@id/bearing_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +  </LinearLayout> + +  <LinearLayout +      xmlns:android="http://schemas.android.com/apk/res/android" +      android:layout_width="fill_parent" +      android:layout_height="wrap_content" +      android:baselineAligned="true" +      android:orientation="horizontal" > + +    <RelativeLayout +	android:layout_width="0dp" +	android:layout_height="wrap_content" +	android:layout_weight="1" +	android:paddingTop="5dp" > + +      <TextView +	  android:id="@+id/target_lat_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:paddingRight="4dp" +	  android:text="@string/target_latitude_label" /> + +      <TextView +	  android:id="@+id/target_lat_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_toRightOf="@id/target_lat_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:layout_width="0dp" +	android:layout_height="wrap_content" +	android:layout_weight="1" +	android:paddingTop="5dp" > + +      <TextView +	  android:id="@+id/target_lon_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:paddingRight="4dp" +	  android:text="@string/target_longitude_label" /> + +      <TextView +	  android:id="@+id/target_lon_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_toRightOf="@id/target_lon_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> +  </LinearLayout> + +  <LinearLayout +      xmlns:android="http://schemas.android.com/apk/res/android" +      android:layout_width="fill_parent" +      android:layout_height="wrap_content" +      android:baselineAligned="true" +      android:orientation="horizontal" > + +    <RelativeLayout +	android:layout_width="0dp" +	android:layout_height="wrap_content" +	android:layout_weight="1" +	android:paddingTop="5dp" > + +      <TextView +	  android:id="@+id/receiver_lat_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:paddingRight="4dp" +	  android:text="@string/receiver_latitude_label" /> + +      <TextView +	  android:id="@+id/receiver_lat_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_toRightOf="@id/receiver_lat_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:layout_width="0dp" +	android:layout_height="wrap_content" +	android:layout_weight="1" +	android:paddingTop="5dp" > + +      <TextView +	  android:id="@+id/receiver_lon_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:paddingRight="4dp" +	  android:text="@string/receiver_longitude_label" /> + +      <TextView +	  android:id="@+id/receiver_lon_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_toRightOf="@id/receiver_lon_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> +  </LinearLayout> +</LinearLayout> diff --git a/altosdroid/res/layout/tab_pad.xml b/altosdroid/res/layout/tab_pad.xml index 38e61f83..88648c3b 100644 --- a/altosdroid/res/layout/tab_pad.xml +++ b/altosdroid/res/layout/tab_pad.xml @@ -15,306 +15,481 @@       with this program; if not, write to the Free Software Foundation, Inc.,       59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.  --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" -	android:layout_width="fill_parent" -	android:layout_height="wrap_content" -	android:layout_weight="0" -	android:orientation="vertical" > - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" > - -		<ImageView -			android:id="@+id/battery_redled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:contentDescription="@string/battery_voltage_label" -			android:src="@drawable/grayled" /> - -		<ImageView -			android:id="@+id/battery_greenled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/battery_redled" -			android:contentDescription="@string/battery_voltage_label" -			android:paddingRight="5dp" -			android:src="@drawable/grayled" /> - -		<TextView -			android:id="@+id/battery_voltage_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/battery_greenled" -			android:text="@string/battery_voltage_label" /> - -		<TextView -			android:id="@+id/battery_voltage_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_below="@id/battery_voltage_label" -			android:layout_toRightOf="@id/battery_greenled" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<ImageView -			android:id="@+id/apogee_redled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:contentDescription="@string/apogee_voltage_label" -			android:src="@drawable/grayled" /> - -		<ImageView -			android:id="@+id/apogee_greenled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/apogee_redled" -			android:contentDescription="@string/apogee_voltage_label" -			android:paddingRight="5dp" -			android:src="@drawable/grayled" /> - -		<TextView -			android:id="@+id/apogee_voltage_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/apogee_greenled" -			android:text="@string/apogee_voltage_label" /> - -		<TextView -			android:id="@+id/apogee_voltage_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_below="@id/apogee_voltage_label" -			android:layout_toRightOf="@id/apogee_greenled" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<ImageView -			android:id="@+id/main_redled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:contentDescription="@string/main_voltage_label" -			android:src="@drawable/grayled" /> - -		<ImageView -			android:id="@+id/main_greenled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/main_redled" -			android:contentDescription="@string/main_voltage_label" -			android:paddingRight="5dp" -			android:src="@drawable/grayled" /> - -		<TextView -			android:id="@+id/main_voltage_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/main_greenled" -			android:text="@string/main_voltage_label" /> - -		<TextView -			android:id="@+id/main_voltage_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_below="@id/main_voltage_label" -			android:layout_toRightOf="@id/main_greenled" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<ImageView -			android:id="@+id/logging_redled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:contentDescription="@string/logging_label" -			android:src="@drawable/grayled" /> - -		<ImageView -			android:id="@+id/logging_greenled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/logging_redled" -			android:contentDescription="@string/logging_label" -			android:paddingRight="5dp" -			android:src="@drawable/grayled" /> - -		<TextView -			android:id="@+id/logging_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/logging_greenled" -			android:text="@string/logging_label" /> - -		<TextView -			android:id="@+id/logging_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_below="@id/logging_label" -			android:layout_toRightOf="@id/logging_greenled" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<ImageView -			android:id="@+id/gps_locked_redled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:contentDescription="@string/gps_locked_label" -			android:src="@drawable/grayled" /> - -		<ImageView -			android:id="@+id/gps_locked_greenled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/gps_locked_redled" -			android:contentDescription="@string/gps_locked_label" -			android:paddingRight="5dp" -			android:src="@drawable/grayled" /> - -		<TextView -			android:id="@+id/gps_locked_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/gps_locked_greenled" -			android:text="@string/gps_locked_label" /> - -		<TextView -			android:id="@+id/gps_locked_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_below="@id/gps_locked_label" -			android:layout_toRightOf="@id/gps_locked_greenled" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingTop="5dp" > - -		<ImageView -			android:id="@+id/gps_ready_redled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:contentDescription="@string/gps_ready_label" -			android:src="@drawable/grayled" /> - -		<ImageView -			android:id="@+id/gps_ready_greenled" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/gps_ready_redled" -			android:contentDescription="@string/gps_ready_label" -			android:paddingRight="5dp" -			android:src="@drawable/grayled" /> - -		<TextView -			android:id="@+id/gps_ready_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/gps_ready_greenled" -			android:text="@string/gps_ready_label" /> - -		<TextView -			android:id="@+id/gps_ready_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_below="@id/gps_ready_label" -			android:layout_toRightOf="@id/gps_ready_greenled" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingLeft="69dp"> - -		<TextView -			android:id="@+id/pad_lat_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:width="100sp" -			android:paddingRight="10sp" -			android:layout_toRightOf="@id/gps_ready_greenled" -			android:text="@string/pad_lat_label" /> - -		<TextView -			android:id="@+id/pad_lat_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/pad_lat_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingLeft="69dp"> - -		<TextView -			android:id="@+id/pad_lon_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:width="100sp" -			android:paddingRight="10sp" -			android:layout_toRightOf="@id/gps_ready_greenled" -			android:text="@string/pad_lon_label" /> - -		<TextView -			android:id="@+id/pad_lon_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/pad_lon_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -	<RelativeLayout -		android:layout_width="wrap_content" -		android:layout_height="wrap_content" -		android:paddingLeft="69dp"> - -		<TextView -			android:id="@+id/pad_alt_label" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:width="100sp" -			android:paddingRight="10sp" -			android:layout_toRightOf="@id/gps_ready_greenled" -			android:text="@string/pad_alt_label" /> - -		<TextView -			android:id="@+id/pad_alt_value" -			android:layout_width="wrap_content" -			android:layout_height="wrap_content" -			android:layout_toRightOf="@id/pad_alt_label" -			android:text="" -			android:textAppearance="?android:attr/textAppearanceSmall" /> -	</RelativeLayout> - -</LinearLayout>
\ No newline at end of file + +<LinearLayout +    xmlns:android="http://schemas.android.com/apk/res/android" +    android:layout_width="wrap_content" +    android:layout_height="wrap_content"> + +  <TableLayout +      xmlns:android="http://schemas.android.com/apk/res/android" +      android:stretchColumns="2,3" +      android:layout_weight="0" +      android:layout_width="wrap_content" +      android:layout_height="wrap_content"> + +    <TableRow +	android:layout_gravity="center" +	android:layout_weight="1" +	android:padding="2dip" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content"> +       +      <ImageView +	  android:id="@+id/battery_redled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/battery_voltage_label" +	  android:src="@drawable/grayled" +	  /> + +      <ImageView +	  android:id="@+id/battery_greenled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/battery_voltage_label" +	  android:src="@drawable/grayled" /> + +      <TextView +	  android:id="@+id/battery_voltage_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/battery_voltage_label" /> + +      <TextView +	  android:id="@+id/battery_voltage_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" +	  /> +    </TableRow> + +    <TableRow +	android:id="@+id/receiver_row" +	android:visibility="gone" +	android:layout_gravity="center" +	android:layout_weight="1" +	android:padding="2dip" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content"> + +      <ImageView +	  android:id="@+id/receiver_redled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/receiver_voltage_label" +	  android:src="@drawable/grayled" /> + +      <ImageView +	  android:id="@+id/receiver_greenled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/receiver_voltage_label" +	  android:src="@drawable/grayled" /> + +      <TextView +	  android:id="@+id/receiver_voltage_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/receiver_voltage_label" /> + +      <TextView +	  android:id="@+id/receiver_voltage_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </TableRow> + +    <TableRow +	android:layout_gravity="center" +	android:layout_weight="1" +	android:padding="2dip" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content" > + +      <ImageView +	  android:id="@+id/logging_redled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/logging_label" +	  android:src="@drawable/grayled" /> + +      <ImageView +	  android:id="@+id/logging_greenled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/logging_label" +	  android:src="@drawable/grayled" /> + +      <TextView +	  android:id="@+id/logging_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/logging_label" /> + +      <TextView +	  android:id="@+id/logging_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_below="@id/logging_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </TableRow> + +    <TableRow +	android:layout_gravity="center" +	android:layout_weight="1" +	android:padding="2dip" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content" > + +      <ImageView +	  android:id="@+id/gps_locked_redled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/gps_locked_label" +	  android:src="@drawable/grayled" /> + +      <ImageView +	  android:id="@+id/gps_locked_greenled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/gps_locked_label" +	  android:src="@drawable/grayled" /> + +      <TextView +	  android:id="@+id/gps_locked_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/gps_locked_label" /> + +      <TextView +	  android:id="@+id/gps_locked_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_below="@id/gps_locked_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </TableRow> + +    <TableRow +	android:layout_gravity="center" +	android:layout_weight="1" +	android:padding="2dip" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content" > + +      <ImageView +	  android:id="@+id/gps_ready_redled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/gps_ready_label" +	  android:src="@drawable/grayled" /> + +      <ImageView +	  android:id="@+id/gps_ready_greenled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/gps_ready_label" +	  android:src="@drawable/grayled" /> + +      <TextView +	  android:id="@+id/gps_ready_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/gps_ready_label" /> + +      <TextView +	  android:id="@+id/gps_ready_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_below="@id/gps_ready_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </TableRow> + +    <TableRow +	android:id="@+id/apogee_row" +	android:visibility="gone" +	android:layout_gravity="center" +	android:layout_weight="1" +	android:padding="2dip" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content"> + +      <ImageView +	  android:id="@+id/apogee_redled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/apogee_voltage_label" +	  android:src="@drawable/grayled" /> + +      <ImageView +	  android:id="@+id/apogee_greenled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/apogee_voltage_label" +	  android:src="@drawable/grayled" /> + +      <TextView +	  android:id="@+id/apogee_voltage_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/apogee_voltage_label" /> + +      <TextView +	  android:id="@+id/apogee_voltage_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </TableRow> + +    <TableRow +	android:id="@+id/main_row" +	android:visibility="gone" +	android:layout_gravity="center" +	android:layout_weight="1" +	android:padding="2dip" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content" > + +      <ImageView +	  android:id="@+id/main_redled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/main_voltage_label" +	  android:src="@drawable/grayled" /> + +      <ImageView +	  android:id="@+id/main_greenled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/main_voltage_label" +	  android:src="@drawable/grayled" /> + +      <TextView +	  android:id="@+id/main_voltage_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/main_voltage_label" /> + +      <TextView +	  android:id="@+id/main_voltage_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </TableRow> + +    <TableRow +	android:id="@+id/ignite_a_row" +	android:visibility="gone" +	android:layout_gravity="center" +	android:layout_weight="1" +	android:padding="2dip" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content" > + +      <ImageView +	  android:id="@+id/ignite_a_redled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/ignite_a_voltage_label" +	  android:src="@drawable/grayled" /> + +      <ImageView +	  android:id="@+id/ignite_a_greenled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/ignite_a_voltage_label" +	  android:src="@drawable/grayled" /> + +      <TextView +	  android:id="@+id/ignite_a_voltage_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/ignite_a_voltage_label" /> + +      <TextView +	  android:id="@+id/ignite_a_voltage_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </TableRow> + +    <TableRow +	android:id="@+id/ignite_b_row" +	android:visibility="gone" +	android:layout_gravity="center" +	android:layout_weight="1" +	android:padding="2dip" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content" > + +      <ImageView +	  android:id="@+id/ignite_b_redled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/ignite_b_voltage_label" +	  android:src="@drawable/grayled" /> + +      <ImageView +	  android:id="@+id/ignite_b_greenled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/ignite_b_voltage_label" +	  android:src="@drawable/grayled" /> + +      <TextView +	  android:id="@+id/ignite_b_voltage_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/ignite_b_voltage_label" /> + +      <TextView +	  android:id="@+id/ignite_b_voltage_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </TableRow> + +    <TableRow +	android:id="@+id/ignite_c_row" +	android:visibility="gone" +	android:layout_gravity="center" +	android:layout_weight="1" +	android:padding="2dip" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content" > + +      <ImageView +	  android:id="@+id/ignite_c_redled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/ignite_c_voltage_label" +	  android:src="@drawable/grayled" /> + +      <ImageView +	  android:id="@+id/ignite_c_greenled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/ignite_c_voltage_label" +	  android:src="@drawable/grayled" /> + +      <TextView +	  android:id="@+id/ignite_c_voltage_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/ignite_c_voltage_label" /> + +      <TextView +	  android:id="@+id/ignite_c_voltage_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </TableRow> + +    <TableRow +	android:id="@+id/ignite_d_row" +	android:visibility="gone" +	android:layout_gravity="center" +	android:layout_weight="1" +	android:padding="2dip" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content" > + +      <ImageView +	  android:id="@+id/ignite_d_redled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/ignite_d_voltage_label" +	  android:src="@drawable/grayled" /> + +      <ImageView +	  android:id="@+id/ignite_d_greenled" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:contentDescription="@string/ignite_d_voltage_label" +	  android:src="@drawable/grayled" /> + +      <TextView +	  android:id="@+id/ignite_d_voltage_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/ignite_d_voltage_label" /> + +      <TextView +	  android:id="@+id/ignite_d_voltage_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </TableRow> + +    <TableRow +	android:padding="2dip" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content"> + +      <TextView +	  android:id="@+id/receiver_lat_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_column="2" +	  android:text="@string/receiver_latitude_label" /> + +      <TextView +	  android:id="@+id/receiver_lat_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </TableRow> + +    <TableRow +	android:padding="2dip" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content"> + +      <TextView +	  android:id="@+id/receiver_lon_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_column="2" +	  android:text="@string/receiver_longitude_label" /> + +      <TextView +	  android:id="@+id/receiver_lon_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </TableRow> + +    <TableRow +	android:padding="2dip" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content"> + +      <TextView +	  android:id="@+id/receiver_alt_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_column="2" +	  android:text="@string/receiver_altitude_label" /> + +      <TextView +	  android:id="@+id/receiver_alt_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </TableRow> +  </TableLayout> +</LinearLayout> diff --git a/altosdroid/res/layout/tab_recover.xml b/altosdroid/res/layout/tab_recover.xml new file mode 100644 index 00000000..201f45ed --- /dev/null +++ b/altosdroid/res/layout/tab_recover.xml @@ -0,0 +1,251 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +     Copyright © 2013 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. +--> +<LinearLayout +    xmlns:android="http://schemas.android.com/apk/res/android" +    android:layout_width="fill_parent" +    android:layout_height="wrap_content" +    android:orientation="vertical" > + +  <LinearLayout +      xmlns:android="http://schemas.android.com/apk/res/android" +      android:layout_weight="0" +      android:layout_width="fill_parent" +      android:layout_height="wrap_content" +      android:orientation="vertical" > + +    <RelativeLayout +	android:layout_gravity="fill" +	android:layout_weight="1" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content" > + +      <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/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:layout_gravity="fill" +	android:layout_weight="1" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content" > + +      <TextView +	  android:id="@+id/direction_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/direction_label" /> + +      <TextView +	  android:id="@+id/direction_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_alignParentRight="true" +	  android:layout_below="@+id/direction_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:layout_gravity="fill" +	android:layout_weight="1" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content"> + +      <TextView +	  android:id="@+id/distance_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/distance_label" /> + +      <TextView +	  android:id="@+id/distance_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_alignParentRight="true" +	  android:layout_below="@+id/distance_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:layout_gravity="fill" +	android:layout_weight="1" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content"> + +      <TextView +	  android:id="@+id/target_lat_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/target_latitude_label" /> + +      <TextView +	  android:id="@+id/target_lat_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_alignParentRight="true" +	  android:layout_below="@id/target_lat_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:layout_gravity="fill" +	android:layout_weight="1" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content"> + +      <TextView +	  android:id="@+id/target_lon_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/target_longitude_label" /> + +      <TextView +	  android:id="@+id/target_lon_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_alignParentRight="true" +	  android:layout_below="@id/target_lon_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:layout_gravity="fill" +	android:layout_weight="1" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content"> + +      <TextView +	  android:id="@+id/receiver_lat_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/receiver_latitude_label" /> + +      <TextView +	  android:id="@+id/receiver_lat_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_alignParentRight="true" +	  android:layout_below="@id/receiver_lat_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:layout_gravity="fill" +	android:layout_weight="1" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content"> + +      <TextView +	  android:id="@+id/receiver_lon_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/receiver_longitude_label" /> + +      <TextView +	  android:id="@+id/receiver_lon_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_alignParentRight="true" +	  android:layout_below="@id/receiver_lon_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:layout_gravity="fill" +	android:layout_weight="1" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content"> + +      <TextView +	  android:id="@+id/max_height_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/max_height_label" /> + +      <TextView +	  android:id="@+id/max_height_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_alignParentRight="true" +	  android:layout_below="@id/max_height_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:layout_gravity="fill" +	android:layout_weight="1" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content"> + +      <TextView +	  android:id="@+id/max_speed_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/max_speed_label" /> + +      <TextView +	  android:id="@+id/max_speed_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_alignParentRight="true" +	  android:layout_below="@id/max_speed_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> + +    <RelativeLayout +	android:layout_gravity="fill" +	android:layout_weight="1" +	android:layout_width="wrap_content" +	android:layout_height="wrap_content"> + +      <TextView +	  android:id="@+id/max_accel_label" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:text="@string/max_accel_label" /> + +      <TextView +	  android:id="@+id/max_accel_value" +	  android:layout_width="wrap_content" +	  android:layout_height="wrap_content" +	  android:layout_alignParentRight="true" +	  android:layout_below="@id/max_accel_label" +	  android:text="" +	  android:textAppearance="?android:attr/textAppearanceSmall" /> +    </RelativeLayout> +  </LinearLayout> +</LinearLayout> diff --git a/altosdroid/res/menu/option_menu.xml b/altosdroid/res/menu/option_menu.xml index 3bd5a54e..7e08c803 100644 --- a/altosdroid/res/menu/option_menu.xml +++ b/altosdroid/res/menu/option_menu.xml @@ -1,25 +1,27 @@  <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2009 The Android Open Source Project +<!--  +  Copyright © 2015 Keith Packard <keithp@keithp.com> -     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 +  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. -          http://www.apache.org/licenses/LICENSE-2.0 +  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. -     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. +  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.  -->  <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/quit" -          android:icon="@android:drawable/ic_menu_close_clear_cancel" -          android:title="@string/quit" /> +    <item android:id="@+id/disconnect" +	  android:icon="@android:drawable/ic_notification_clear_all" +	  android:title="@string/disconnect_device" />      <item android:id="@+id/select_freq"            android:icon="@android:drawable/ic_menu_preferences"            android:title="@string/select_freq" /> @@ -29,4 +31,22 @@      <item android:id="@+id/change_units"  	  android:icon="@android:drawable/ic_menu_view"  	  android:title="@string/change_units" /> +    <item android:id="@+id/preload_maps" +	  android:icon="@android:drawable/ic_menu_mapmode" +	  android:title="@string/preload_maps" /> +    <item android:id="@+id/map_type" +	  android:icon="@android:drawable/ic_menu_mapmode" +	  android:title="@string/map_type" /> +    <item android:id="@+id/map_source" +	  android:icon="@android:drawable/ic_menu_mapmode" +	  android:title="@string/map_source" /> +    <item android:id="@+id/select_tracker" +	  android:icon="@android:drawable/ic_menu_view" +	  android:title="@string/select_tracker"/> +    <item android:id="@+id/delete_track" +	  android:icon="@android:drawable/ic_notification_clear_all" +	  android:title="@string/delete_track"/> +    <item android:id="@+id/quit" +          android:icon="@android:drawable/ic_menu_close_clear_cancel" +          android:title="@string/quit" />  </menu> diff --git a/altosdroid/res/values/Colors.xml b/altosdroid/res/values/Colors.xml new file mode 100644 index 00000000..055c6df2 --- /dev/null +++ b/altosdroid/res/values/Colors.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +	 Copyright © 2015 Keith Packard <keithp@keithp.com> + +	 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. +--> +<resources> +  <color name="old_color">#ffff4040</color> +</resources> diff --git a/altosdroid/res/values/CustomTheme.xml b/altosdroid/res/values/CustomTheme.xml new file mode 100644 index 00000000..6c701ea2 --- /dev/null +++ b/altosdroid/res/values/CustomTheme.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> + +<resources> +  <style name="CustomTheme" parent="android:Theme.Holo"> +  </style> +</resources> diff --git a/altosdroid/res/values/strings.xml b/altosdroid/res/values/strings.xml index 0cc99349..e7014fc9 100644 --- a/altosdroid/res/values/strings.xml +++ b/altosdroid/res/values/strings.xml @@ -27,10 +27,19 @@  	<!-- Options Menu -->  	<string name="connect_device">Connect a device</string> +	<string name="disconnect_device">Disconnect device</string>  	<string name="quit">Quit</string>  	<string name="select_freq">Select radio frequency</string>  	<string name="select_rate">Select data rate</string>  	<string name="change_units">Change units</string> +	<string name="preload_maps">Load Maps</string> +	<string name="select_tracker">Select Tracker</string> +	<string name="delete_track">Delete Track</string> +	<string name="map_type">Map Type</string> +	<string name="map_source">Toggle Online/Offline maps</string> + +	<!-- MapTypeActivity --> +	<string name="map_type">Map Type</string>  	<!-- DeviceListActivity -->  	<string name="scanning">scanning for devices…</string> @@ -60,6 +69,7 @@  	<string name="speed_label">Speed</string>  	<string name="accel_label">Acceleration</string>  	<string name="bearing_label">Bearing</string> +	<string name="direction_label">Direction</string>  	<string name="elevation_label">Elevation</string>  	<string name="range_label">Range</string>  	<string name="distance_label">Distance</string> @@ -67,10 +77,15 @@  	<string name="max_height_label">Max Height</string>  	<string name="max_speed_label">Max Speed</string>  	<string name="max_accel_label">Max Accel</string> -	<string name="battery_voltage_label">Battery Voltage</string> -	<string name="apogee_voltage_label">Apogee Igniter Voltage</string> -	<string name="main_voltage_label">Main Igniter Voltage</string> -	<string name="logging_label">On-board Data Logging</string> +	<string name="battery_voltage_label">Battery</string> +	<string name="receiver_voltage_label">Receiver Battery</string> +	<string name="apogee_voltage_label">Apogee Igniter</string> +	<string name="main_voltage_label">Main Igniter</string> +	<string name="ignite_a_voltage_label">Igniter A</string> +	<string name="ignite_b_voltage_label">Igniter B</string> +	<string name="ignite_c_voltage_label">Igniter C</string> +	<string name="ignite_d_voltage_label">Igniter D</string> +	<string name="logging_label">Data Logging</string>  	<string name="gps_locked_label">GPS Locked</string>  	<string name="gps_ready_label">GPS Ready</string>  	<string name="latitude_label">Latitude</string> @@ -79,8 +94,21 @@  	<string name="target_longitude_label">Tar Lon</string>  	<string name="receiver_latitude_label">My Lat</string>  	<string name="receiver_longitude_label">My Lon</string> -	<string name="pad_lat_label">Pad Lat</string> -	<string name="pad_lon_label">Pad Lon</string> -	<string name="pad_alt_label">Pad Alt</string> +	<string name="receiver_altitude_label">My Alt</string> + +	<!-- Map preload --> +	<string name="preload_site_label">Known Launch Sites</string> +	<string name="preload_latitude_label">Latitude</string> +	<string name="preload_longitude_label">Longitude</string> +	<string name="preload_types">Map Types</string> +	<string name="preload_hybrid">Hybrid</string> +	<string name="preload_satellite">Satellite</string> +	<string name="preload_roadmap">Roadmap</string> +	<string name="preload_terrain">Terrain</string> +	<string name="preload_min_zoom">Minimum Zoom</string> +	<string name="preload_max_zoom">Maximum Zoom</string> +	<string name="preload_radius">Radius</string> +	 +	<string name="preload_load">Load Map</string>  </resources> diff --git a/altosdroid/res/xml/device_filter.xml b/altosdroid/res/xml/device_filter.xml new file mode 100644 index 00000000..84b09c06 --- /dev/null +++ b/altosdroid/res/xml/device_filter.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> + +<resources> +  <usb-device vendor-id="65534" /> +  <usb-device vendor-id="1027" /> +</resources> diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java index 3740f55d..976e64bb 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java @@ -29,236 +29,191 @@ import android.bluetooth.BluetoothSocket;  //import android.os.Bundle;  import android.os.Handler;  //import android.os.Message; -import android.util.Log; -import org.altusmetrum.altoslib_6.*; +import org.altusmetrum.altoslib_8.*; -public class AltosBluetooth extends AltosLink { - -	// Debugging -	private static final String TAG = "AltosBluetooth"; -	private static final boolean D = true; +public class AltosBluetooth extends AltosDroidLink {  	private ConnectThread    connect_thread = null; -	private Thread           input_thread   = null; - -	private Handler          handler; -	private BluetoothAdapter adapter; -	private BluetoothDevice  device; +	private BluetoothDevice	 device;  	private BluetoothSocket  socket;  	private InputStream      input;  	private OutputStream     output; +	private boolean		 pause;  	// Constructor -	public AltosBluetooth(BluetoothDevice in_device, Handler in_handler) { -//		set_debug(D); -		adapter = BluetoothAdapter.getDefaultAdapter(); -		device = in_device; -		handler = in_handler; +	public AltosBluetooth(BluetoothDevice device, Handler handler, boolean pause) { +		super(handler); +		this.device = device; +		this.handler = handler; +		this.pause = pause; -		connect_thread = new ConnectThread(device); +		connect_thread = new ConnectThread();  		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; +	void connected() { +		if (closed()) { +			AltosDebug.debug("connected after closed"); +			return;  		} -		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(); - +		AltosDebug.check_ui("connected\n"); +		try { +			synchronized(this) { +				if (socket != null) {  					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; +					super.connected();  				} +			} +		} catch (InterruptedException ie) { +			connect_failed(); +		} catch (IOException io) { +			connect_failed(); +		} +	} -				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(); +	private void connect_failed() { +		if (closed()) { +			AltosDebug.debug("connect_failed after closed"); +			return; +		} -				// Notify other waiting threads, now that we're connected -				AltosBluetooth.this.notifyAll(); +		close_device(); +		input = null; +		output = null; +		handler.obtainMessage(TelemetryService.MSG_CONNECT_FAILED, this).sendToTarget(); +		AltosDebug.error("ConnectThread: Failed to establish connection"); +	} -				// Reset the ConnectThread because we're done -				connect_thread = null; +	void close_device() { +		BluetoothSocket	tmp_socket; -				if (D) Log.d(TAG, "ConnectThread: Connect completed"); -			} +		synchronized(this) { +			tmp_socket = socket; +			socket = null;  		} -		public void cancel() { +		if (tmp_socket != null) {  			try { -				if (socket != null) -					socket.close(); +				tmp_socket.close();  			} catch (IOException e) { -				if (D) Log.e(TAG, "ConnectThread: close() of connect socket failed", e); +				AltosDebug.error("close_socket failed");  			}  		}  	} -	public double frequency() { -		return frequency; -	} - -	public int telemetry_rate() { -		return telemetry_rate; -	} - -	public void save_frequency() { -		AltosPreferences.set_frequency(0, frequency); +	public void close() { +		super.close(); +		input = null; +		output = null;  	} -	public void save_telemetry_rate() { -		AltosPreferences.set_telemetry_rate(0, telemetry_rate); -	} +	private final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); -	private synchronized void wait_connected() throws InterruptedException, IOException { -		if (input == null) { -			if (D) Log.d(TAG, "wait_connected..."); -			wait(); -			if (D) Log.d(TAG, "wait_connected done"); -			if (input == null) throw new IOException(); -		} -	} +	private void create_socket(BluetoothDevice  device) { -	private void connection_lost() { -		if (D) Log.e(TAG, "Connection lost during I/O"); -		handler.obtainMessage(TelemetryService.MSG_DISCONNECTED).sendToTarget(); -	} +		BluetoothSocket tmp_socket = null; -	public void print(String data) { -		byte[] bytes = data.getBytes(); -		if (D) Log.d(TAG, "print(): begin"); +		AltosDebug.check_ui("create_socket\n");  		try { -			wait_connected(); -			output.write(bytes); -			if (D) Log.d(TAG, "print(): Wrote bytes: '" + data.replace('\n', '\\') + "'"); +			tmp_socket = device.createInsecureRfcommSocketToServiceRecord(SPP_UUID);  		} catch (IOException e) { -			connection_lost(); -		} catch (InterruptedException e) { -			connection_lost(); +			e.printStackTrace();  		} -	} - -	public void putchar(byte c) { -		byte[] bytes = { c }; -		if (D) Log.d(TAG, "print(): begin"); -		try { -			wait_connected(); -			output.write(bytes); -			if (D) Log.d(TAG, "print(): Wrote byte: '" + c + "'"); -		} catch (IOException e) { -			connection_lost(); -		} catch (InterruptedException e) { -			connection_lost(); +		if (socket != null) { +			AltosDebug.debug("Socket already allocated %s", socket.toString()); +			close_device(); +		} +		synchronized (this) { +			socket = tmp_socket;  		}  	} -	private static final int buffer_size = 1024; +	private class ConnectThread extends Thread { -	private byte[] buffer = new byte[buffer_size]; -	private int buffer_len = 0; -	private int buffer_off = 0; +		public void run() { +			AltosDebug.debug("ConnectThread: BEGIN (pause %b)", pause); +			setName("ConnectThread"); -	public int getchar() { -		while (buffer_off == buffer_len) { +			if (pause) { +				try { +					Thread.sleep(4000); +				} catch (InterruptedException e) { +				} +			} + +			create_socket(device); +			// Always cancel discovery because it will slow down a connection  			try { -				wait_connected(); -				buffer_len = input.read(buffer); -				buffer_off = 0; -			} catch (IOException e) { -				connection_lost(); -				return AltosLink.ERROR; -			} catch (java.lang.InterruptedException e) { -				connection_lost(); -				return AltosLink.ERROR; +				BluetoothAdapter.getDefaultAdapter().cancelDiscovery(); +			} catch (Exception e) { +				AltosDebug.debug("cancelDiscovery exception %s", e.toString());  			} -		} -		return buffer[buffer_off++]; -	} -	public void close() { -		if (D) Log.d(TAG, "close(): begin"); -		synchronized(this) { -			if (D) Log.d(TAG, "close(): synched"); +			BluetoothSocket	local_socket = null; -			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"); +			synchronized (AltosBluetooth.this) { +				if (!closed()) +					local_socket = socket;  			} -			if (input_thread != null) { -				if (D) Log.d(TAG, "close(): stopping input_thread"); + +			if (local_socket != null) {  				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; +					// Make a connection to the BluetoothSocket +					// This is a blocking call and will only return on a +					// successful connection or an exception +					local_socket.connect(); +				} catch (Exception e) { +					AltosDebug.debug("Connect exception %s", e.toString()); +					try { +						local_socket.close(); +					} catch (Exception ce) { +						AltosDebug.debug("Close exception %s", ce.toString()); +					} +					local_socket = null; +				} +			} + +			if (local_socket != null) { +				connected(); +			} else { +				connect_failed();  			} -			input = null; -			output = null; -			notifyAll(); + +			AltosDebug.debug("ConnectThread: completed");  		}  	} +	private synchronized void wait_connected() throws InterruptedException, IOException { +		AltosDebug.check_ui("wait_connected\n"); +		if (input == null && socket != null) { +			AltosDebug.debug("wait_connected..."); +			wait(); +			AltosDebug.debug("wait_connected done"); +		} +		if (socket == null) +			throw new IOException(); +	} -	// 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); +	int write(byte[] buffer, int len) { +		try { +			output.write(buffer, 0, len); +		} catch (IOException ie) { +			return -1; +		} +		return len;  	} -	//public void flush_output() { super.flush_output(); } +	int read(byte[] buffer, int len) { +		try { +			return input.read(buffer, 0, len); +		} catch (IOException ie) { +			return -1; +		} +	}  	// Stubs of required methods when extending AltosLink  	public boolean can_cancel_reply()   { return false; } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDebug.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDebug.java new file mode 100644 index 00000000..db63d810 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDebug.java @@ -0,0 +1,77 @@ +/* + * Copyright © 2015 Keith Packard <keithp@keithp.com> + * + * 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.util.Arrays; +import java.io.*; +import java.lang.*; + +import org.altusmetrum.altoslib_8.*; + +import android.app.Activity; +import android.graphics.*; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.view.*; +import android.widget.*; +import android.location.Location; +import android.content.*; +import android.util.Log; +import android.os.*; +import android.content.pm.*; + +public class AltosDebug { +	// Debugging +	static final String TAG = "AltosDroid"; + +	static boolean	D = true; + +	static void init(Context context) { +		ApplicationInfo	app_info = context.getApplicationInfo(); + +		if ((app_info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { +			Log.d(TAG, "Enable debugging\n"); +			D = true; +		} else { +			Log.d(TAG, "Disable debugging\n"); +			D = false; +		} +	} + + +	static void info(String format, Object ... arguments) { +		Log.i(TAG, String.format(format, arguments)); +	} + +	static void debug(String format, Object ... arguments) { +		if (D) +			Log.d(TAG, String.format(format, arguments)); +	} + +	static void error(String format, Object ... arguments) { +		Log.e(TAG, String.format(format, arguments)); +	} + +	static void check_ui(String format, Object ... arguments) { +		if (Looper.myLooper() == Looper.getMainLooper()) { +			Log.e(TAG, String.format("ON UI THREAD " + format, arguments)); +			for (StackTraceElement el : Thread.currentThread().getStackTrace()) +				Log.e(TAG, "\t" + el.toString() + "\n"); +		} +	} +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java index 53963f25..3a07212a 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java @@ -18,11 +18,12 @@  package org.altusmetrum.AltosDroid;  import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Timer; -import java.util.TimerTask; +import java.text.*; +import java.util.*; +import java.io.*;  import android.app.Activity; +import android.app.PendingIntent;  import android.bluetooth.BluetoothAdapter;  import android.bluetooth.BluetoothDevice;  import android.content.Intent; @@ -36,28 +37,26 @@ import android.os.Handler;  import android.os.Message;  import android.os.Messenger;  import android.os.RemoteException; +import android.content.res.Resources;  import android.support.v4.app.FragmentActivity;  import android.support.v4.app.FragmentManager;  import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.Window; -import android.view.View; -import android.widget.TabHost; -import android.widget.TextView; -import android.widget.RelativeLayout; -import android.widget.Toast; +import android.view.*; +import android.widget.*;  import android.app.AlertDialog;  import android.location.Location; +import android.hardware.usb.*; +import android.graphics.*; +import android.graphics.drawable.*; -import org.altusmetrum.altoslib_6.*; +import org.altusmetrum.altoslib_8.*;  public class AltosDroid extends FragmentActivity implements AltosUnitsListener { -	// Debugging -	static final String TAG = "AltosDroid"; -	static final boolean D = true; + +	// Actions sent to the telemetry server at startup time + +	public static final String ACTION_BLUETOOTH = "org.altusmetrum.AltosDroid.BLUETOOTH"; +	public static final String ACTION_USB = "org.altusmetrum.AltosDroid.USB";  	// Message types received by our Handler @@ -67,14 +66,15 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  	// Intent request codes  	public static final int REQUEST_CONNECT_DEVICE = 1;  	public static final int REQUEST_ENABLE_BT      = 2; +	public static final int REQUEST_PRELOAD_MAPS   = 3; +	public static final int REQUEST_MAP_TYPE       = 4; + +	public int map_type = AltosMap.maptype_hybrid;  	public static FragmentManager	fm;  	private BluetoothAdapter mBluetoothAdapter = null; -	// Layout Views -	private TextView mTitle; -  	// Flight state values  	private TextView mCallsignView;  	private TextView mRSSIView; @@ -83,6 +83,14 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  	private RelativeLayout mStateLayout;  	private TextView mStateView;  	private TextView mAgeView; +	private boolean  mAgeViewOld; +	private int mAgeNewColor; +	private int mAgeOldColor; + +	public static final String	tab_pad_name = "pad"; +	public static final String	tab_flight_name = "flight"; +	public static final String	tab_recover_name = "recover"; +	public static final String	tab_map_name = "map";  	// field to display the version at the bottom of the screen  	private TextView mVersion; @@ -100,6 +108,11 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  	// Timer and Saved flight state for Age calculation  	private Timer timer;  	AltosState saved_state; +	TelemetryState	telemetry_state; +	Integer[] 	serials; + +	UsbDevice	pending_usb_device; +	boolean		start_with_usb;  	// Service  	private boolean mIsBound   = false; @@ -120,17 +133,13 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  			switch (msg.what) {  			case MSG_STATE: -				if(D) Log.d(TAG, "MSG_STATE"); -				TelemetryState telemetry_state = (TelemetryState) msg.obj; -				if (telemetry_state == null) { -					Log.d(TAG, "telemetry_state null!"); +				if (msg.obj == null) { +					AltosDebug.debug("telemetry_state null!");  					return;  				} - -				ad.update_state(telemetry_state); +				ad.update_state((TelemetryState) msg.obj);  				break;  			case MSG_UPDATE_AGE: -				if(D) Log.d(TAG, "MSG_UPDATE_AGE");  				ad.update_age();  				break;  			} @@ -148,6 +157,13 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  			} catch (RemoteException e) {  				// In this case the service has crashed before we could even do anything with it  			} +			if (pending_usb_device != null) { +				try { +					mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, pending_usb_device)); +					pending_usb_device = null; +				} catch (RemoteException e) { +				} +			}  		}  		public void onServiceDisconnected(ComponentName className) { @@ -201,17 +217,20 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  				if (telemetry_state.telemetry_rate != AltosLib.ao_telemetry_rate_38400)  					str = str.concat(String.format(" %d bps",  								       AltosLib.ao_telemetry_rate_values[telemetry_state.telemetry_rate])); -				mTitle.setText(str); +				setTitle(str);  			} else { -				mTitle.setText(R.string.title_connected_to); +				setTitle(R.string.title_connected_to);  			}  			break;  		case TelemetryState.CONNECT_CONNECTING: -			mTitle.setText(R.string.title_connecting); +			if (telemetry_state.address != null) +				setTitle(String.format("Connecting to %s...", telemetry_state.address.name)); +			else +				setTitle("Connecting to something...");  			break; -		case TelemetryState.CONNECT_READY: +		case TelemetryState.CONNECT_DISCONNECTED:  		case TelemetryState.CONNECT_NONE: -			mTitle.setText(R.string.title_not_connected); +			setTitle(R.string.title_not_connected);  			break;  		}  	} @@ -231,21 +250,73 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  		}  	} +	int	selected_serial = 0; +	int	current_serial; +	long	switch_time; + +	void set_switch_time() { +		switch_time = System.currentTimeMillis(); +		selected_serial = 0; +	} +  	boolean	registered_units_listener; -	void update_state(TelemetryState telemetry_state) { +	void update_state(TelemetryState new_telemetry_state) { + +		if (new_telemetry_state != null) +			telemetry_state = new_telemetry_state; + +		if (selected_serial != 0) +			current_serial = selected_serial; + +		if (current_serial == 0) +			current_serial = telemetry_state.latest_serial;  		if (!registered_units_listener) {  			registered_units_listener = true;  			AltosPreferences.register_units_listener(this);  		} +		serials = telemetry_state.states.keySet().toArray(new Integer[0]); +		Arrays.sort(serials); +  		update_title(telemetry_state); -		update_ui(telemetry_state.state, telemetry_state.location); -		if (telemetry_state.connect == TelemetryState.CONNECT_CONNECTED) -			start_timer(); -		else -			stop_timer(); + +		AltosState	state = null; +		boolean		aged = true; + +		if (telemetry_state.states.containsKey(current_serial)) { +			state = telemetry_state.states.get(current_serial); +			int age = state_age(state); +			if (age < 20) +				aged = false; +			if (current_serial == selected_serial) +				aged = false; +			else if (switch_time != 0 && (switch_time - state.received_time) > 0) +				aged = true; +		} + +		if (aged) { +			AltosState	newest_state = null; +			int		newest_age = 0; + +			for (int serial : telemetry_state.states.keySet()) { +				AltosState	existing = telemetry_state.states.get(serial); +				int		existing_age = state_age(existing); + +				if (newest_state == null || existing_age < newest_age) { +					newest_state = existing; +					newest_age = existing_age; +				} +			} + +			if (newest_state != null) +				state = newest_state; +		} + +		update_ui(telemetry_state, state, telemetry_state.location); + +		start_timer();  	}  	boolean same_string(String a, String b) { @@ -260,14 +331,55 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  		}  	} -	void update_age() { -		if (saved_state != null) -			mAgeView.setText(String.format("%d", (System.currentTimeMillis() - saved_state.received_time + 500) / 1000)); + +	private int blend_component(int a, int b, double r, int shift, int mask) { +		return ((int) (((a >> shift) & mask) * r + ((b >> shift) & mask) * (1 - r)) & mask) << shift;  	} +	private int blend_color(int a, int b, double r) { +		return (blend_component(a, b, r, 0, 0xff) | +			blend_component(a, b, r, 8, 0xff) | +			blend_component(a, b, r, 16, 0xff) | +			blend_component(a, b, r, 24, 0xff)); +	} + +	int state_age(AltosState state) { +		return (int) ((System.currentTimeMillis() - state.received_time + 500) / 1000); +	} + +	void set_screen_on(int age) { +		if (age < 60) +			getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); +		else +			getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); +	} + +	void update_age() { +		if (saved_state != null) { +			int age = state_age(saved_state); + +			double age_scale = age / 100.0; + +			if (age_scale > 1.0) +				age_scale = 1.0; -	void update_ui(AltosState state, Location location) { +			mAgeView.setTextColor(blend_color(mAgeOldColor, mAgeNewColor, age_scale)); -		Log.d(TAG, "update_ui"); +			set_screen_on(age); + +			String	text; +			if (age < 60) +				text = String.format("%ds", age); +			else if (age < 60 * 60) +				text = String.format("%dm", age / 60); +			else if (age < 60 * 60 * 24) +				text = String.format("%dh", age / (60 * 60)); +			else +				text = String.format("%dd", age / (24 * 60 * 60)); +			mAgeView.setText(text); +		} +	} + +	void update_ui(TelemetryState telem_state, AltosState state, Location location) {  		int prev_state = AltosLib.ao_flight_invalid; @@ -277,7 +389,8 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  			prev_state = saved_state.state;  		if (state != null) { -			Log.d(TAG, String.format("prev state %d new state  %d\n", prev_state, state.state)); +			set_screen_on(state_age(state)); +  			if (state.state == AltosLib.ao_flight_stateless) {  				boolean	prev_locked = false;  				boolean locked = false; @@ -289,27 +402,23 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  				if (prev_locked != locked) {  					String currentTab = mTabHost.getCurrentTabTag();  					if (locked) { -						if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("descent"); +						if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);  					} else { -						if (currentTab.equals("descent")) mTabHost.setCurrentTabByTag("pad"); +						if (currentTab.equals(tab_flight_name)) mTabHost.setCurrentTabByTag(tab_pad_name);  					}  				}  			} else {  				if (prev_state != state.state) {  					String currentTab = mTabHost.getCurrentTabTag(); -					Log.d(TAG, "switch state");  					switch (state.state) {  					case AltosLib.ao_flight_boost: -						if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("ascent"); -						break; -					case AltosLib.ao_flight_drogue: -						if (currentTab.equals("ascent")) mTabHost.setCurrentTabByTag("descent"); +						if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);  						break;  					case AltosLib.ao_flight_landed: -						if (currentTab.equals("descent")) mTabHost.setCurrentTabByTag("landed"); +						if (currentTab.equals(tab_flight_name)) mTabHost.setCurrentTabByTag(tab_recover_name);  						break;  					case AltosLib.ao_flight_stateless: -						if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("descent"); +						if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);  						break;  					}  				} @@ -328,22 +437,18 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  			}  			if (saved_state == null || !same_string(saved_state.callsign, state.callsign)) { -				Log.d(TAG, "update callsign");  				mCallsignView.setText(state.callsign);  			}  			if (saved_state == null || state.serial != saved_state.serial) { -				Log.d(TAG, "update serial");  				mSerialView.setText(String.format("%d", state.serial));  			}  			if (saved_state == null || state.flight != saved_state.flight) { -				Log.d(TAG, "update flight");  				if (state.flight == AltosLib.MISSING)  					mFlightView.setText("");  				else  					mFlightView.setText(String.format("%d", state.flight));  			}  			if (saved_state == null || state.state != saved_state.state) { -				Log.d(TAG, "update state");  				if (state.state == AltosLib.ao_flight_stateless) {  					mStateLayout.setVisibility(View.GONE);  				} else { @@ -352,16 +457,15 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  				}  			}  			if (saved_state == null || state.rssi != saved_state.rssi) { -				Log.d(TAG, "update rssi");  				mRSSIView.setText(String.format("%d", state.rssi));  			}  		}  		for (AltosDroidTab mTab : mTabs) -			mTab.update_ui(state, from_receiver, location, mTab == mTabsAdapter.currentItem()); +			mTab.update_ui(telem_state, state, from_receiver, location, mTab == mTabsAdapter.currentItem()); -		if (state != null) -			mAltosVoice.tell(state, from_receiver); +		if (mAltosVoice != null) +			mAltosVoice.tell(telem_state, state, from_receiver, location, (AltosDroidTab) mTabsAdapter.currentItem());  		saved_state = state;  	} @@ -398,26 +502,32 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  		return String.format(format, value);  	} +	private View create_tab_view(String label) { +		LayoutInflater inflater = (LayoutInflater) this.getLayoutInflater(); +		View tab_view = inflater.inflate(R.layout.tab_layout, null); +		TextView text_view = (TextView) tab_view.findViewById (R.id.tabLabel); +		text_view.setText(label); +		return tab_view; +	} + +	public void set_map_source(int source) { +		for (AltosDroidTab mTab : mTabs) +			mTab.set_map_source(source); +	} +  	@Override  	public void onCreate(Bundle savedInstanceState) {  		super.onCreate(savedInstanceState); -		if(D) Log.e(TAG, "+++ ON CREATE +++"); +		AltosDebug.init(this); +		AltosDebug.debug("+++ 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(); -		} +		// Initialise preferences +		AltosDroidPreferences.init(this);  		fm = getSupportFragmentManager();  		// Set up the window layout -		requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);  		setContentView(R.layout.altosdroid); -		getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);  		// Create the Tabs and ViewPager  		mTabHost = (TabHost)findViewById(android.R.id.tabhost); @@ -428,37 +538,10 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  		mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager); -		mTabsAdapter.addTab(mTabHost.newTabSpec("pad").setIndicator("Pad"), TabPad.class, null); -		mTabsAdapter.addTab(mTabHost.newTabSpec("ascent").setIndicator("Ascent"), TabAscent.class, null); -		mTabsAdapter.addTab(mTabHost.newTabSpec("descent").setIndicator("Descent"), TabDescent.class, null); -		mTabsAdapter.addTab(mTabHost.newTabSpec("landed").setIndicator("Landed"), TabLanded.class, null); -		mTabsAdapter.addTab(mTabHost.newTabSpec("map").setIndicator("Map"), TabMap.class, null); - - -		// Scale the size of the Tab bar for different screen densities -		// This probably won't be needed when we start supporting ICS+ tabs. -		DisplayMetrics metrics = new DisplayMetrics(); -		getWindowManager().getDefaultDisplay().getMetrics(metrics); -		int density = metrics.densityDpi; - -		if (density==DisplayMetrics.DENSITY_XHIGH) -			tabHeight = 65; -		else if (density==DisplayMetrics.DENSITY_HIGH) -			tabHeight = 45; -		else if (density==DisplayMetrics.DENSITY_MEDIUM) -			tabHeight = 35; -		else if (density==DisplayMetrics.DENSITY_LOW) -			tabHeight = 25; -		else -			tabHeight = 65; - -		for (int i = 0; i < 5; i++) -			mTabHost.getTabWidget().getChildAt(i).getLayoutParams().height = tabHeight; - -		// 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); +		mTabsAdapter.addTab(mTabHost.newTabSpec(tab_pad_name).setIndicator(create_tab_view("Pad")), TabPad.class, null); +		mTabsAdapter.addTab(mTabHost.newTabSpec(tab_flight_name).setIndicator(create_tab_view("Flight")), TabFlight.class, null); +		mTabsAdapter.addTab(mTabHost.newTabSpec(tab_recover_name).setIndicator(create_tab_view("Recover")), TabRecover.class, null); +		mTabsAdapter.addTab(mTabHost.newTabSpec(tab_map_name).setIndicator(create_tab_view("Map")), TabMap.class, null);  		// Display the Version  		mVersion = (TextView) findViewById(R.id.version); @@ -473,58 +556,158 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  		mStateLayout   = (RelativeLayout) findViewById(R.id.state_container);  		mStateView     = (TextView) findViewById(R.id.state_value);  		mAgeView       = (TextView) findViewById(R.id.age_value); +		mAgeNewColor   = mAgeView.getTextColors().getDefaultColor(); +		mAgeOldColor   = getResources().getColor(R.color.old_color); +	} + +	private boolean ensureBluetooth() { +		// 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(); +			return false; +		} + +		if (!mBluetoothAdapter.isEnabled()) { +			Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); +			startActivityForResult(enableIntent, AltosDroid.REQUEST_ENABLE_BT); +		} + +		return true; +	} + +	private boolean check_usb() { +		UsbDevice	device = AltosUsb.find_device(this, AltosLib.product_basestation); + +		if (device != null) { +			Intent		i = new Intent(this, AltosDroid.class); +			PendingIntent	pi = PendingIntent.getActivity(this, 0, new Intent("hello world", null, this, AltosDroid.class), 0); -		mAltosVoice = new AltosVoice(this); +			if (AltosUsb.request_permission(this, device, pi)) { +				connectUsb(device); +			} +			start_with_usb = true; +			return true; +		} + +		start_with_usb = false; + +		return false; +	} + +	private void noticeIntent(Intent intent) { + +		/* Ok, this is pretty convenient. +		 * +		 * When a USB device is plugged in, and our 'hotplug' +		 * intent registration fires, we get an Intent with +		 * EXTRA_DEVICE set. +		 * +		 * When we start up and see a usb device and request +		 * permission to access it, that queues a +		 * PendingIntent, which has the EXTRA_DEVICE added in, +		 * along with the EXTRA_PERMISSION_GRANTED field as +		 * well. +		 * +		 * So, in both cases, we get the device name using the +		 * same call. We check to see if access was granted, +		 * in which case we ignore the device field and do our +		 * usual startup thing. +		 */ + +		UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); +		boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); + +		AltosDebug.debug("intent %s device %s granted %s", intent, device, granted); + +		if (!granted) +			device = null; + +		if (device != null) { +			AltosDebug.debug("intent has usb device " + device.toString()); +			connectUsb(device); +		} else { + +			/* 'granted' is only false if this intent came +			 * from the request_permission call and +			 * permission was denied. In which case, we +			 * don't want to loop forever... +			 */ +			if (granted) { +				AltosDebug.debug("check for a USB device at startup"); +				if (check_usb()) +					return; +			} +			AltosDebug.debug("Starting by looking for bluetooth devices"); +			if (ensureBluetooth()) +				return; +			finish(); +		}  	}  	@Override  	public void onStart() {  		super.onStart(); -		if(D) Log.e(TAG, "++ ON START ++"); +		AltosDebug.debug("++ ON START ++"); + +		set_switch_time(); + +		noticeIntent(getIntent());  		// Start Telemetry Service -		startService(new Intent(AltosDroid.this, TelemetryService.class)); +		String	action = start_with_usb ? ACTION_USB : ACTION_BLUETOOTH; -		if (!mBluetoothAdapter.isEnabled()) { -			Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); -			startActivityForResult(enableIntent, AltosDroid.REQUEST_ENABLE_BT); -		} +		startService(new Intent(action, null, AltosDroid.this, TelemetryService.class));  		doBindService(); +		if (mAltosVoice == null) +			mAltosVoice = new AltosVoice(this); +  	}  	@Override -	public synchronized void onResume() { +	public void onNewIntent(Intent intent) { +		super.onNewIntent(intent); +		AltosDebug.debug("onNewIntent"); +		noticeIntent(intent); +	} + +	@Override +	public void onResume() {  		super.onResume(); -		if(D) Log.e(TAG, "+ ON RESUME +"); +		AltosDebug.debug("+ ON RESUME +");  	}  	@Override -	public synchronized void onPause() { +	public void onPause() {  		super.onPause(); -		if(D) Log.e(TAG, "- ON PAUSE -"); +		AltosDebug.debug("- ON PAUSE -");  	}  	@Override  	public void onStop() {  		super.onStop(); -		if(D) Log.e(TAG, "-- ON STOP --"); - -		doUnbindService(); +		AltosDebug.debug("-- ON STOP --");  	}  	@Override  	public void onDestroy() {  		super.onDestroy(); -		if(D) Log.e(TAG, "--- ON DESTROY ---"); +		AltosDebug.debug("--- ON DESTROY ---"); -		if (mAltosVoice != null) mAltosVoice.stop(); +		doUnbindService(); +		if (mAltosVoice != null) { +			mAltosVoice.stop(); +			mAltosVoice = null; +		}  		stop_timer();  	} -	public void onActivityResult(int requestCode, int resultCode, Intent data) { -		if(D) Log.d(TAG, "onActivityResult " + resultCode); +	protected void onActivityResult(int requestCode, int resultCode, Intent data) { +		AltosDebug.debug("onActivityResult " + resultCode);  		switch (requestCode) {  		case REQUEST_CONNECT_DEVICE:  			// When DeviceListActivity returns with a device to connect to @@ -539,28 +722,64 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  				//setupChat();  			} else {  				// User did not enable Bluetooth or an error occured -				Log.e(TAG, "BT not enabled"); +				AltosDebug.error("BT not enabled");  				stopService(new Intent(AltosDroid.this, TelemetryService.class));  				Toast.makeText(this, R.string.bt_not_enabled, Toast.LENGTH_SHORT).show();  				finish();  			}  			break; +		case REQUEST_MAP_TYPE: +			if (resultCode == Activity.RESULT_OK) +				set_map_type(data); +			break;  		}  	} -	private void connectDevice(String address) { +	private void connectUsb(UsbDevice device) { +		if (mService == null) +			pending_usb_device = device; +		else { +			// Attempt to connect to the device +			try { +				mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, device)); +				AltosDebug.debug("Sent OPEN_USB message"); +			} catch (RemoteException e) { +				AltosDebug.debug("connect device message failed"); +			} +		} +	} + +	private void connectDevice(Intent data) {  		// Attempt to connect to the device  		try { -			if (D) Log.d(TAG, "Connecting to " + address); -			mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, address)); +			String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS); +			String name = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_NAME); + +			AltosDebug.debug("Connecting to " + address + " " + name); +			DeviceAddress	a = new DeviceAddress(address, name); +			mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, a)); +			AltosDebug.debug("Sent connecting message");  		} catch (RemoteException e) { +			AltosDebug.debug("connect device message failed");  		}  	} -	private void connectDevice(Intent data) { -		// Get the device MAC address -		String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS); -		connectDevice(address); +	private void disconnectDevice() { +		try { +			mService.send(Message.obtain(null, TelemetryService.MSG_DISCONNECT, null)); +		} catch (RemoteException e) { +		} +	} + +	private void set_map_type(Intent data) { +		int type = data.getIntExtra(MapTypeActivity.EXTRA_MAP_TYPE, -1); + +		AltosDebug.debug("intent set_map_type %d\n", type); +		if (type != -1) { +			map_type = type; +			for (AltosDroidTab mTab : mTabs) +				mTab.set_map_type(map_type); +		}  	}  	@Override @@ -573,20 +792,22 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  	void setFrequency(double freq) {  		try {  			mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq)); +			set_switch_time();  		} catch (RemoteException e) {  		}  	}  	void setFrequency(String freq) {  		try { -			setFrequency (Double.parseDouble(freq.substring(11, 17))); -		} catch (NumberFormatException e) { +			setFrequency (AltosParse.parse_double_net(freq.substring(11, 17))); +		} catch (ParseException e) {  		}  	}  	void setBaud(int baud) {  		try {  			mService.send(Message.obtain(null, TelemetryService.MSG_SETBAUD, baud)); +			set_switch_time();  		} catch (RemoteException e) {  		}  	} @@ -611,18 +832,77 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  		}  	} +	void select_tracker(int serial) { +		int i; + +		AltosDebug.debug("select tracker %d\n", serial); + +		if (serial == selected_serial) { +			AltosDebug.debug("%d already selected\n", serial); +			return; +		} + +		if (serial != 0) { +			for (i = 0; i < serials.length; i++) +				if (serials[i] == serial) +					break; + +			if (i == serials.length) { +				AltosDebug.debug("attempt to select unknown tracker %d\n", serial); +				return; +			} +		} + +		current_serial = selected_serial = serial; +		update_state(null); +	} + +	void touch_trackers(Integer[] serials) { +		AlertDialog.Builder builder_tracker = new AlertDialog.Builder(this); +		builder_tracker.setTitle("Select Tracker"); +		final String[] trackers = new String[serials.length + 1]; +		trackers[0] = "Auto"; +		for (int i = 0; i < serials.length; i++) +			trackers[i+1] = String.format("%d", serials[i]); +		builder_tracker.setItems(trackers, +					 new DialogInterface.OnClickListener() { +						 public void onClick(DialogInterface dialog, int item) { +							 if (item == 0) +								 select_tracker(0); +							 else +								 select_tracker(Integer.parseInt(trackers[item])); +						 } +					 }); +		AlertDialog alert_tracker = builder_tracker.create(); +		alert_tracker.show(); +	} + +	void delete_track(int serial) { +		try { +			mService.send(Message.obtain(null, TelemetryService.MSG_DELETE_SERIAL, (Integer) serial)); +		} catch (Exception ex) { +		} +	} +  	@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); +			if (ensureBluetooth()) { +				// 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.disconnect: +			/* Disconnect the device +			 */ +			disconnectDevice();  			return true;  		case R.id.quit: -			Log.d(TAG, "R.id.quit"); -			stopService(new Intent(AltosDroid.this, TelemetryService.class)); +			AltosDebug.debug("R.id.quit"); +			disconnectDevice();  			finish();  			return true;  		case R.id.select_freq: @@ -676,8 +956,92 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  			boolean	imperial = AltosPreferences.imperial_units();  			AltosPreferences.set_imperial_units(!imperial);  			return true; +		case R.id.preload_maps: +			serverIntent = new Intent(this, PreloadMapActivity.class); +			startActivityForResult(serverIntent, REQUEST_PRELOAD_MAPS); +			return true; +		case R.id.map_type: +			serverIntent = new Intent(this, MapTypeActivity.class); +			startActivityForResult(serverIntent, REQUEST_MAP_TYPE); +			return true; +		case R.id.map_source: +			int source = AltosDroidPreferences.map_source(); +			int new_source = source == AltosDroidPreferences.MAP_SOURCE_ONLINE ? AltosDroidPreferences.MAP_SOURCE_OFFLINE : AltosDroidPreferences.MAP_SOURCE_ONLINE; +			AltosDroidPreferences.set_map_source(new_source); +			set_map_source(new_source); +			return true; +		case R.id.select_tracker: +			if (serials != null) { +				String[] trackers = new String[serials.length+1]; +				trackers[0] = "Auto"; +				for (int i = 0; i < serials.length; i++) +					trackers[i+1] = String.format("%d", serials[i]); +				AlertDialog.Builder builder_serial = new AlertDialog.Builder(this); +				builder_serial.setTitle("Select a tracker"); +				builder_serial.setItems(trackers, +							new DialogInterface.OnClickListener() { +								public void onClick(DialogInterface dialog, int item) { +									if (item == 0) +										select_tracker(0); +									else +										select_tracker(serials[item-1]); +								} +							}); +				AlertDialog alert_serial = builder_serial.create(); +				alert_serial.show(); + +			} +			return true; +		case R.id.delete_track: +			if (serials != null) { +				String[] trackers = new String[serials.length]; +				for (int i = 0; i < serials.length; i++) +					trackers[i] = String.format("%d", serials[i]); +				AlertDialog.Builder builder_serial = new AlertDialog.Builder(this); +				builder_serial.setTitle("Delete a track"); +				builder_serial.setItems(trackers, +							new DialogInterface.OnClickListener() { +								public void onClick(DialogInterface dialog, int item) { +									delete_track(serials[item]); +								} +							}); +				AlertDialog alert_serial = builder_serial.create(); +				alert_serial.show(); + +			} +			return true;  		}  		return false;  	} +	static String direction(AltosGreatCircle from_receiver, +			     Location receiver) { +		if (from_receiver == null) +			return null; + +		if (receiver == null) +			return null; + +		if (!receiver.hasBearing()) +			return null; + +		float	bearing = receiver.getBearing(); +		float	heading = (float) from_receiver.bearing - bearing; + +		while (heading <= -180.0f) +			heading += 360.0f; +		while (heading > 180.0f) +			heading -= 360.0f; + +		int iheading = (int) (heading + 0.5f); + +		if (-1 < iheading && iheading < 1) +			return "ahead"; +		else if (iheading < -179 || 179 < iheading) +			return "backwards"; +		else if (iheading < 0) +			return String.format("left %d°", -iheading); +		else +			return String.format("right %d°", iheading); +	}  } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidLink.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidLink.java new file mode 100644 index 00000000..7cbba794 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidLink.java @@ -0,0 +1,219 @@ +/* + * Copyright © 2015 Keith Packard <keithp@keithp.com> + * + * 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.os.Handler; + +import org.altusmetrum.altoslib_8.*; + +public abstract class AltosDroidLink extends AltosLink { + +	Handler		handler; + +	Thread          input_thread   = null; + +	public double frequency() { +		return frequency; +	} + +	public int telemetry_rate() { +		return telemetry_rate; +	} + +	public void save_frequency() { +		AltosPreferences.set_frequency(0, frequency); +	} + +	public void save_telemetry_rate() { +		AltosPreferences.set_telemetry_rate(0, telemetry_rate); +	} + +	Object closed_lock = new Object(); +	boolean closing = false; +	boolean closed = false; + +	public boolean closed() { +		synchronized(closed_lock) { +			return closing; +		} +	} + +	void connected() throws InterruptedException { +		input_thread = new Thread(this); +		input_thread.start(); + +		// Configure the newly connected device for telemetry +		print("~\nE 0\n"); +		set_monitor(false); +		AltosDebug.debug("ConnectThread: connected"); + +		/* Let TelemetryService know we're connected +		 */ +		handler.obtainMessage(TelemetryService.MSG_CONNECTED, this).sendToTarget(); + +		/* Notify other waiting threads that we're connected now +		 */ +		notifyAll(); +	} + +	public void closing() { +		synchronized(closed_lock) { +			AltosDebug.debug("Marked closing true"); +			closing = true; +		} +	} + +	private boolean actually_closed() { +		synchronized(closed_lock) { +			return closed; +		} +	} + +	abstract void close_device(); + +	public void close() { +		AltosDebug.debug("close(): begin"); + +		closing(); + +		flush_output(); + +		synchronized (closed_lock) { +			AltosDebug.debug("Marked closed true"); +			closed = true; +		} + +		close_device(); + +		synchronized(this) { + +			if (input_thread != null) { +				AltosDebug.debug("close(): stopping input_thread"); +				try { +					AltosDebug.debug("close(): input_thread.interrupt()....."); +					input_thread.interrupt(); +					AltosDebug.debug("close(): input_thread.join()....."); +					input_thread.join(); +				} catch (Exception e) {} +				input_thread = null; +			} +			notifyAll(); +		} +	} + +	abstract int write(byte[] buffer, int len); + +	abstract int read(byte[] buffer, int len); + +	private static final int buffer_size = 64; + +	private byte[] in_buffer = new byte[buffer_size]; +	private byte[] out_buffer = new byte[buffer_size]; +	private int buffer_len = 0; +	private int buffer_off = 0; +	private int out_buffer_off = 0; + +	private byte[] debug_chars = new byte[buffer_size]; +	private int debug_off; + +	private void debug_input(byte b) { +		if (b == '\n') { +			AltosDebug.debug("            " + new String(debug_chars, 0, debug_off)); +			debug_off = 0; +		} else { +			if (debug_off < buffer_size) +				debug_chars[debug_off++] = b; +		} +	} + +	private void disconnected() { +		if (closed()) { +			AltosDebug.debug("disconnected after closed"); +			return; +		} + +		AltosDebug.debug("Sending disconnected message"); +		handler.obtainMessage(TelemetryService.MSG_DISCONNECTED, this).sendToTarget(); +	} + +	public int getchar() { + +		if (actually_closed()) +			return ERROR; + +		while (buffer_off == buffer_len) { +			buffer_len = read(in_buffer, buffer_size); +			if (buffer_len < 0) { +				AltosDebug.debug("ERROR returned from getchar()"); +				disconnected(); +				return ERROR; +			} +			buffer_off = 0; +		} +		if (AltosDebug.D) +			debug_input(in_buffer[buffer_off]); +		return in_buffer[buffer_off++]; +	} + +	public void flush_output() { +		super.flush_output(); + +		if (actually_closed()) { +			out_buffer_off = 0; +			return; +		} + +		while (out_buffer_off != 0) { +			int	sent = write(out_buffer, out_buffer_off); + +			if (sent <= 0) { +				AltosDebug.debug("flush_output() failed"); +				out_buffer_off = 0; +				break; +			} + +			if (sent < out_buffer_off) +				System.arraycopy(out_buffer, 0, out_buffer, sent, out_buffer_off - sent); + +			out_buffer_off -= sent; +		} +	} + +	public void putchar(byte c) { +		out_buffer[out_buffer_off++] = c; +		if (out_buffer_off == buffer_size) +			flush_output(); +	} + +	public void print(String data) { +		byte[] bytes = data.getBytes(); +		AltosDebug.debug("print(): begin"); +		for (byte b : bytes) +			putchar(b); +		AltosDebug.debug("print(): Wrote bytes: '" + data.replace('\n', '\\') + "'"); +	} + +	public AltosDroidLink(Handler handler) { +		this.handler = handler; +	} +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidMapInterface.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidMapInterface.java new file mode 100644 index 00000000..5f6ff198 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidMapInterface.java @@ -0,0 +1,33 @@ +/* + * Copyright © 2015 Keith Packard <keithp@keithp.com> + * + * 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.util.*; +import java.io.*; +import android.location.Location; +import org.altusmetrum.altoslib_8.*; + +public interface AltosDroidMapInterface { +	public void onCreateView(AltosDroid altos_droid); + +	public void set_visible(boolean visible); + +	public void center(double lat, double lon, double accuracy); + +	public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver); +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferences.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferences.java index 7ab70147..a4e27006 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferences.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferences.java @@ -17,14 +17,23 @@  package org.altusmetrum.AltosDroid;  import android.content.Context; -import org.altusmetrum.altoslib_6.*; +import org.altusmetrum.altoslib_8.*;  public class AltosDroidPreferences extends AltosPreferences {  	/* Active device preference name */ -	final static String activeDevicePreference = "ACTIVE-DEVICE"; +	final static String activeDeviceAddressPreference = "ACTIVE-DEVICE-ADDRESS"; +	final static String activeDeviceNamePreference = "ACTIVE-DEVICE-NAME"; -	static String active_device_address; +	static DeviceAddress	active_device_address; + +	/* Map source preference name */ +	final static String mapSourcePreference = "MAP-SOURCE"; + +	static final int	MAP_SOURCE_OFFLINE = 0; +	static final int	MAP_SOURCE_ONLINE = 1; + +	static int	map_source;  	public static void init(Context context) {  		if (backend != null) @@ -32,20 +41,41 @@ public class AltosDroidPreferences extends AltosPreferences {  		AltosPreferences.init(new AltosDroidPreferencesBackend(context)); -		active_device_address = backend.getString(activeDevicePreference, null); +		String address = backend.getString(activeDeviceAddressPreference, null); +		String name = backend.getString(activeDeviceNamePreference, null); + +		if (address != null && name != null) +			active_device_address = new DeviceAddress (address, name); + +		map_source = backend.getInt(mapSourcePreference, MAP_SOURCE_ONLINE);  	} -	public static void set_active_device(String address) { +	public static void set_active_device(DeviceAddress address) {  		synchronized(backend) {  			active_device_address = address; -			backend.putString(activeDevicePreference, active_device_address); +			backend.putString(activeDeviceAddressPreference, active_device_address.address); +			backend.putString(activeDeviceNamePreference, active_device_address.name);  			flush_preferences();  		}  	} -	public static String active_device() { +	public static DeviceAddress active_device() {  		synchronized(backend) {  			return active_device_address;  		}  	} + +	public static void set_map_source(int map_source) { +		synchronized(backend) { +			AltosDroidPreferences.map_source = map_source; +			backend.putInt(mapSourcePreference, map_source); +			flush_preferences(); +		} +	} + +	public static int map_source() { +		synchronized(backend) { +			return map_source; +		} +	}  } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferencesBackend.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferencesBackend.java index bc5300fd..2ff711f5 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferencesBackend.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferencesBackend.java @@ -24,7 +24,7 @@ import android.content.SharedPreferences;  import android.os.Environment;  import android.util.*; -import org.altusmetrum.altoslib_6.*; +import org.altusmetrum.altoslib_8.*;  public class AltosDroidPreferencesBackend implements AltosPreferencesBackend {  	public final static String        NAME    = "org.altusmetrum.AltosDroid"; @@ -44,7 +44,12 @@ public class AltosDroidPreferencesBackend implements AltosPreferencesBackend {  	public String[] keys() {  		Map<String, ?> all = prefs.getAll(); -		return (String[])all.keySet().toArray(); +		Object[] ao = all.keySet().toArray(); + +		String[] as = new String[ao.length]; +		for (int i = 0; i < ao.length; i++) +			as[i] = (String) ao[i]; +		return as;  	}  	public AltosPreferencesBackend node(String key) { @@ -104,6 +109,7 @@ public class AltosDroidPreferencesBackend implements AltosPreferencesBackend {  	}  	public void remove(String key) { +		AltosDebug.debug("remove preference %s\n", key);  		editor.remove(key);  	} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java index cbb20045..9d612a1e 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java @@ -17,7 +17,7 @@  package org.altusmetrum.AltosDroid; -import org.altusmetrum.altoslib_6.*; +import org.altusmetrum.altoslib_8.*;  import android.location.Location;  import android.app.Activity;  import android.graphics.Color; @@ -26,21 +26,28 @@ import android.support.v4.app.Fragment;  import android.support.v4.app.FragmentTransaction;  import android.support.v4.app.FragmentManager;  import android.location.Location; -import android.util.Log;  import android.widget.TextView;  public abstract class AltosDroidTab extends Fragment implements AltosUnitsListener { +	TelemetryState		last_telem_state;  	AltosState		last_state;  	AltosGreatCircle	last_from_receiver;  	Location		last_receiver; +	AltosDroid		altos_droid; -	public abstract void show(AltosState state, AltosGreatCircle from_receiver, Location receiver); +	public abstract void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver);  	public abstract String tab_name(); +	public void set_map_type(int map_type) { +	} + +	public void set_map_source(int map_source) { +	} +  	public void units_changed(boolean imperial_units) { -		if (!isHidden() && last_state != null) -			show(last_state, last_from_receiver, last_receiver); +		if (!isHidden()) +			show(last_telem_state, last_state, last_from_receiver, last_receiver);  	}  	public void set_value(TextView text_view, @@ -55,29 +62,46 @@ public abstract class AltosDroidTab extends Fragment implements AltosUnitsListen  	public void set_visible(boolean visible) {  		FragmentTransaction	ft = AltosDroid.fm.beginTransaction(); +		AltosDebug.debug("set visible %b %s\n", visible, tab_name());  		if (visible) { -			AltosState		state = last_state; -			AltosGreatCircle	from_receiver = last_from_receiver; -			Location		receiver = last_receiver; - -			show(state, from_receiver, receiver);  			ft.show(this); +			show(last_telem_state, last_state, last_from_receiver, last_receiver);  		} else  			ft.hide(this); -		ft.commit(); +		ft.commitAllowingStateLoss(); +	} + +	@Override +	public void onAttach(Activity activity) { +		super.onAttach(activity); +		altos_droid = (AltosDroid) activity; +		altos_droid.registerTab(this);  	} -	public void update_ui(AltosState state, AltosGreatCircle from_receiver, Location receiver, boolean is_current) { +	@Override +	public void onDetach() { +		super.onDetach(); +		altos_droid.unregisterTab(this); +		altos_droid = null; +	} + +	@Override +	public void onResume() { +		super.onResume(); +		AltosDebug.debug("onResume tab %s\n", tab_name()); +		set_visible(true); +	} + +	public void update_ui(TelemetryState telem_state, AltosState state, +			      AltosGreatCircle from_receiver, Location receiver, boolean is_current) +	{ +		last_telem_state = telem_state;  		last_state = state;  		last_from_receiver = from_receiver;  		last_receiver = receiver; -		if (is_current) { -			if (AltosDroid.D) Log.d(AltosDroid.TAG, String.format("%s: visible, performing update", tab_name())); - -			show(state, from_receiver, receiver); -		} else { -			if (AltosDroid.D) Log.d(AltosDroid.TAG, String.format("%s: not visible, skipping update", tab_name())); +		if (is_current) +			show(telem_state, state, from_receiver, receiver); +		else  			return; -		}  	}  } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOffline.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOffline.java new file mode 100644 index 00000000..eb059901 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOffline.java @@ -0,0 +1,517 @@ +/* + * Copyright © 2015 Keith Packard <keithp@keithp.com> + * + * 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.util.*; +import java.io.*; + +import org.altusmetrum.altoslib_8.*; + +import android.app.Activity; +import android.graphics.*; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.view.*; +import android.widget.*; +import android.location.Location; +import android.content.*; +import android.util.*; + +class Rocket implements Comparable { +	AltosLatLon	position; +	String		name; +	int		serial; +	long		last_packet; +	boolean		active; +	AltosMapOffline	map_offline; + +	void paint() { +		map_offline.draw_bitmap(position, map_offline.rocket_bitmap, map_offline.rocket_off_x, map_offline.rocket_off_y); +		map_offline.draw_text(position, name, 0, 3*map_offline.rocket_bitmap.getHeight()/4); +	} + +	void set_position(AltosLatLon position, long last_packet) { +		this.position = position; +		this.last_packet = last_packet; +	} + +	void set_active(boolean active) { +		this.active = active; +	} + +	public int compareTo(Object o) { +		Rocket other = (Rocket) o; + +		if (active && !other.active) +			return 1; +		if (other.active && !active) +			return -1; + +		long	diff = last_packet - other.last_packet; + +		if (diff > 0) +			return 1; +		if (diff < 0) +			return -1; +		return 0; +	} + +	Rocket(int serial, AltosMapOffline map_offline) { +		this.serial = serial; +		this.name = String.format("%d", serial); +		this.map_offline = map_offline; +	} +} + +public class AltosMapOffline extends View implements ScaleGestureDetector.OnScaleGestureListener, AltosMapInterface, AltosDroidMapInterface { +	ScaleGestureDetector	scale_detector; +	boolean			scaling; +	AltosMap		map; +	AltosDroid		altos_droid; + +	AltosLatLon	here; +	AltosLatLon	there; +	AltosLatLon	pad; + +	Canvas	canvas; +	Paint	paint; + +	Bitmap	pad_bitmap; +	int	pad_off_x, pad_off_y; +	Bitmap	rocket_bitmap; +	int	rocket_off_x, rocket_off_y; +	Bitmap	here_bitmap; +	int	here_off_x, here_off_y; + +	static  final int	WHITE = 0xffffffff; +	static  final int	RED   = 0xffff0000; +	static  final int	PINK  = 0xffff8080; +	static  final int	YELLOW= 0xffffff00; +	static  final int	CYAN  = 0xff00ffff; +	static  final int	BLUE  = 0xff0000ff; +	static  final int	BLACK = 0xff000000; + +	public static final int stateColors[] = { +		WHITE,  // startup +		WHITE,  // idle +		WHITE,  // pad +		RED,    // boost +		PINK,   // fast +		YELLOW, // coast +		CYAN,   // drogue +		BLUE,   // main +		BLACK,  // landed +		BLACK,  // invalid +		CYAN,   // stateless +	}; + +	/* AltosMapInterface */ +	public void debug(String format, Object ... arguments) { +		AltosDebug.debug(format, arguments); +	} + +	class MapTile extends AltosMapTile { +		public void paint(AltosMapTransform t) { +			AltosPointInt		pt = new AltosPointInt(t.screen(upper_left)); + +			if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA)) +				return; + +			AltosImage		altos_image = cache.get(this, store, px_size, px_size); + +			MapImage 		map_image = (MapImage) altos_image; + +			Bitmap			bitmap = null; + +			if (map_image != null) +				bitmap = map_image.bitmap; + +			if (bitmap != null) { +				canvas.drawBitmap(bitmap, pt.x, pt.y, paint); +			} else { +				paint.setColor(0xff808080); +				canvas.drawRect(pt.x, pt.y, pt.x + px_size, pt.y + px_size, paint); +				if (t.has_location()) { +					String	message = null; +					switch (status) { +					case AltosMapTile.loading: +						message = "Loading..."; +						break; +					case AltosMapTile.bad_request: +						message = "Internal error"; +						break; +					case AltosMapTile.failed: +						message = "Network error, check connection"; +						break; +					case AltosMapTile.forbidden: +						message = "Too many requests, try later"; +						break; +					} +					if (message != null) { +						Rect	bounds = new Rect(); +						paint.getTextBounds(message, 0, message.length(), bounds); + +						int	width = bounds.right - bounds.left; +						int	height = bounds.bottom - bounds.top; + +						float x = pt.x + px_size / 2.0f; +						float y = pt.y + px_size / 2.0f; +						x = x - width / 2.0f; +						y = y + height / 2.0f; +						paint.setColor(0xff000000); +						canvas.drawText(message, 0, message.length(), x, y, paint); +					} +				} +			} +		} + +		public MapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) { +			super(listener, upper_left, center, zoom, maptype, px_size, 2); +		} + +	} + +	public AltosMapTile new_tile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) { +		return new MapTile(listener, upper_left, center, zoom, maptype, px_size); +	} + +	public AltosMapPath new_path() { +		return null; +	} + +	public AltosMapLine new_line() { +		return null; +	} + +	class MapImage implements AltosImage { +		public Bitmap	bitmap; + +		public void flush() { +			if (bitmap != null) { +				bitmap.recycle(); +				bitmap = null; +			} +		} + +		public MapImage(File file) { +			bitmap = BitmapFactory.decodeFile(file.getPath()); +		} +	} + +	public AltosImage load_image(File file) throws Exception { +		return new MapImage(file); +	} + +	class MapMark extends AltosMapMark { +		public void paint(AltosMapTransform t) { +		} + +		MapMark(double lat, double lon, int state) { +			super(lat, lon, state); +		} +	} + +	public AltosMapMark new_mark(double lat, double lon, int state) { +		return new MapMark(lat, lon, state); +	} + +	public int width() { +		return getWidth(); +	} + +	public int height() { +		return getHeight(); +	} + +	public void repaint() { +		postInvalidate(); +	} + +	public void repaint(AltosRectangle damage) { +		postInvalidate(damage.x, damage.y, damage.x + damage.width, damage.y + damage.height); +	} + +	public void set_zoom_label(String label) { +	} + +	public void select_object(AltosLatLon latlon) { +		if (map.transform == null) +			return; +		ArrayList<Integer>	near = new ArrayList<Integer>(); + +		for (Rocket rocket : sorted_rockets()) { +			if (rocket.position == null) { +				debug("rocket %d has no position\n", rocket.serial); +				continue; +			} +			double distance = map.transform.hypot(latlon, rocket.position); +			debug("check select %d distance %g width %d\n", rocket.serial, distance, rocket_bitmap.getWidth()); +			if (distance < rocket_bitmap.getWidth() * 2.0) { +				debug("selecting %d\n", rocket.serial); +				near.add(rocket.serial); +			} +		} +		if (near.size() != 0) +			altos_droid.touch_trackers(near.toArray(new Integer[0])); +	} + +	class Line { +		AltosLatLon	a, b; + +		void paint() { +			if (a != null && b != null) { +				AltosPointDouble	a_screen = map.transform.screen(a); +				AltosPointDouble	b_screen = map.transform.screen(b); +				paint.setColor(0xff8080ff); +				canvas.drawLine((float) a_screen.x, (float) a_screen.y, +						    (float) b_screen.x, (float) b_screen.y, +						    paint); +			} +		} + +		void set_a(AltosLatLon a) { +			this.a = a; +		} + +		void set_b(AltosLatLon b) { +			this.b = b; +		} + +		Line() { +		} +	} + +	Line line = new Line(); + +	int	stroke_width = 20; + +	void draw_text(AltosLatLon lat_lon, String text, int off_x, int off_y) { +		if (lat_lon != null && map != null && map.transform != null) { +			AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon)); + +			Rect	bounds = new Rect(); +			paint.getTextBounds(text, 0, text.length(), bounds); + +			int	width = bounds.right - bounds.left; +			int	height = bounds.bottom - bounds.top; + +			float x = pt.x; +			float y = pt.y; +			x = x - width / 2.0f - off_x; +			y = y + height / 2.0f - off_y; +			paint.setColor(0xff000000); +			canvas.drawText(text, 0, text.length(), x, y, paint); +		} +	} + +	HashMap<Integer,Rocket> rockets = new HashMap<Integer,Rocket>(); + +	void draw_bitmap(AltosLatLon lat_lon, Bitmap bitmap, int off_x, int off_y) { +		if (lat_lon != null && map != null && map.transform != null) { +			AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon)); + +			canvas.drawBitmap(bitmap, pt.x - off_x, pt.y - off_y, paint); +		} +	} + +	private Rocket[] sorted_rockets() { +		Rocket[]	rocket_array = rockets.values().toArray(new Rocket[0]); + +		Arrays.sort(rocket_array); +		return rocket_array; +	} + +	private void draw_positions() { +		line.set_a(there); +		line.set_b(here); +		line.paint(); +		draw_bitmap(pad, pad_bitmap, pad_off_x, pad_off_y); + +		for (Rocket rocket : sorted_rockets()) +			rocket.paint(); +		draw_bitmap(here, here_bitmap, here_off_x, here_off_y); +	} + +	@Override public void invalidate() { +		Rect r = new Rect(); +		getDrawingRect(r); +		super.invalidate(); +	} + +	@Override public void invalidate(int l, int t, int r, int b) { +		Rect rect = new Rect(); +		getDrawingRect(rect); +		super.invalidate(); +	} + +	@Override +	protected void onDraw(Canvas view_canvas) { +		if (map == null) { +			debug("MapView draw without map\n"); +			return; +		} +		canvas = view_canvas; +		paint = new Paint(Paint.ANTI_ALIAS_FLAG); +		paint.setStrokeWidth(stroke_width); +		paint.setStrokeCap(Paint.Cap.ROUND); +		paint.setStrokeJoin(Paint.Join.ROUND); +		paint.setTextSize(40); +		map.paint(); +		draw_positions(); +		canvas = null; +	} + +	public boolean onScale(ScaleGestureDetector detector) { +		float	f = detector.getScaleFactor(); + +		if (f <= 0.8) { +			map.set_zoom_centre(map.get_zoom() - 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY())); +			return true; +		} +		if (f >= 1.2) { +			map.set_zoom_centre(map.get_zoom() + 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY())); +			return true; +		} +		return false; +	} + +	public boolean onScaleBegin(ScaleGestureDetector detector) { +		return true; +	} + +	public void onScaleEnd(ScaleGestureDetector detector) { +	} + +	@Override +	public boolean dispatchTouchEvent(MotionEvent event) { +		scale_detector.onTouchEvent(event); + +		if (scale_detector.isInProgress()) { +			scaling = true; +		} + +		if (scaling) { +			if (event.getAction() == MotionEvent.ACTION_UP) { +				scaling = false; +			} +			return true; +		} + +		if (event.getAction() == MotionEvent.ACTION_DOWN) { +			map.touch_start((int) event.getX(), (int) event.getY(), true); +		} else if (event.getAction() == MotionEvent.ACTION_MOVE) { +			map.touch_continue((int) event.getX(), (int) event.getY(), true); +		} else if (event.getAction() == MotionEvent.ACTION_UP) { +			map.touch_stop((int) event.getX(), (int) event.getY(), true); +		} +		return true; +	} + +	double	mapAccuracy; + +	public void center(double lat, double lon, double accuracy) { +		if (mapAccuracy <= 0 || accuracy < mapAccuracy/10 || (map != null && !map.has_centre())) { +			if (map != null) +				map.maybe_centre(lat, lon); +			mapAccuracy = accuracy; +		} +	} + +	public void set_visible(boolean visible) { +		if (visible) +			setVisibility(VISIBLE); +		else +			setVisibility(GONE); +	} + +	public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) { +		if (state != null) { +			map.show(state, null); +			if (state.pad_lat != AltosLib.MISSING && pad == null) +				pad = new AltosLatLon(state.pad_lat, state.pad_lon); +		} + +		if (telem_state != null) { +			Integer[] old_serial = rockets.keySet().toArray(new Integer[0]); +			Integer[] new_serial = telem_state.states.keySet().toArray(new Integer[0]); + +			/* remove deleted keys */ +			for (int serial : old_serial) { +				if (!telem_state.states.containsKey(serial)) +					rockets.remove(serial); +			} + +			/* set remaining keys */ + +			for (int serial : new_serial) { +				Rocket 		rocket; +				AltosState	t_state = telem_state.states.get(serial); +				if (rockets.containsKey(serial)) +					rocket = rockets.get(serial); +				else { +					rocket = new Rocket(serial, this); +					rockets.put(serial, rocket); +				} +				if (t_state.gps != null) { +					AltosLatLon	latlon = new AltosLatLon(t_state.gps.lat, t_state.gps.lon); +					rocket.set_position(latlon, t_state.received_time); +					if (state.serial == serial) +						there = latlon; +				} +				if (state != null) +					rocket.set_active(state.serial == serial); +			} +		} +		if (receiver != null) { +			here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude()); +		} +	} + +	public void onCreateView(AltosDroid altos_droid) { +		this.altos_droid = altos_droid; +		map = new AltosMap(this); +		map.set_maptype(altos_droid.map_type); + +		pad_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pad); +		/* arrow at the bottom of the launchpad image */ +		pad_off_x = pad_bitmap.getWidth() / 2; +		pad_off_y = pad_bitmap.getHeight(); + +		rocket_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rocket); +		/* arrow at the bottom of the rocket image */ +		rocket_off_x = rocket_bitmap.getWidth() / 2; +		rocket_off_y = rocket_bitmap.getHeight(); + +		here_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_maps_indicator_current_position); +		/* Center of the dot */ +		here_off_x = here_bitmap.getWidth() / 2; +		here_off_y = here_bitmap.getHeight() / 2; +	} + +	public void set_map_type(int map_type) { +		if (map != null) +			map.set_maptype(map_type); +	} + +	public AltosMapOffline(Context context, AttributeSet attrs) { +		super(context, attrs); +		this.altos_droid = altos_droid; +		scale_detector = new ScaleGestureDetector(context, this); +	} +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOnline.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOnline.java new file mode 100644 index 00000000..4ac95c0b --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOnline.java @@ -0,0 +1,327 @@ +/* + * Copyright © 2013 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.util.*; + +import org.altusmetrum.altoslib_8.*; + +import com.google.android.gms.maps.*; +import com.google.android.gms.maps.model.*; + +import android.app.Activity; +import android.graphics.Color; +import android.graphics.*; +import android.os.Bundle; +import android.support.v4.app.Fragment; +//import android.support.v4.app.FragmentTransaction; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.location.Location; +import android.content.*; + +class RocketOnline implements Comparable { +	Marker		marker; +	int		serial; +	long		last_packet; +	int		size; + +	void set_position(AltosLatLon position, long last_packet) { +		marker.setPosition(new LatLng(position.lat, position.lon)); +		this.last_packet = last_packet; +	} + +	private Bitmap rocket_bitmap(Context context, String text) { + +		/* From: http://mapicons.nicolasmollet.com/markers/industry/military/missile-2/ +		 */ +		Bitmap orig_bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.rocket); +		Bitmap bitmap = orig_bitmap.copy(Bitmap.Config.ARGB_8888, true); + +		Canvas canvas = new Canvas(bitmap); +		Paint paint = new Paint(); +		paint.setTextSize(40); +		paint.setColor(0xff000000); + +		Rect	bounds = new Rect(); +		paint.getTextBounds(text, 0, text.length(), bounds); + +		int	width = bounds.right - bounds.left; +		int	height = bounds.bottom - bounds.top; + +		float x = bitmap.getWidth() / 2.0f - width / 2.0f; +		float y = bitmap.getHeight() / 2.0f - height / 2.0f; + +		size = bitmap.getWidth(); + +		canvas.drawText(text, 0, text.length(), x, y, paint); +		return bitmap; +	} + +	public void remove() { +		marker.remove(); +	} + +	public int compareTo(Object o) { +		RocketOnline other = (RocketOnline) o; + +		long	diff = last_packet - other.last_packet; + +		if (diff > 0) +			return 1; +		if (diff < 0) +			return -1; +		return 0; +	} + +	RocketOnline(Context context, int serial, GoogleMap map, double lat, double lon, long last_packet) { +		this.serial = serial; +		String name = String.format("%d", serial); +		this.marker = map.addMarker(new MarkerOptions() +					    .icon(BitmapDescriptorFactory.fromBitmap(rocket_bitmap(context, name))) +					    .position(new LatLng(lat, lon)) +					    .visible(true)); +		this.last_packet = last_packet; +	} +} + +public class AltosMapOnline implements AltosDroidMapInterface, GoogleMap.OnMarkerClickListener, GoogleMap.OnMapClickListener { +	public SupportMapFragment mMapFragment; +	private GoogleMap mMap; +	private boolean mapLoaded = false; +	Context context; + +	private HashMap<Integer,RocketOnline> rockets = new HashMap<Integer,RocketOnline>(); +	private Marker mPadMarker; +	private boolean pad_set; +	private Polyline mPolyline; + +	private View map_view; + +	private double mapAccuracy = -1; + +	private AltosLatLon my_position = null; +	private AltosLatLon target_position = null; + +	private AltosDroid altos_droid; + +	public void onCreateView(AltosDroid altos_droid) { +		this.altos_droid = altos_droid; +		final int map_type = altos_droid.map_type; +		mMapFragment = new SupportMapFragment() { +			@Override +			public void onActivityCreated(Bundle savedInstanceState) { +				super.onActivityCreated(savedInstanceState); +				setupMap(map_type); +			} +			@Override +			public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { +				map_view = super.onCreateView(inflater, container, savedInstanceState); +				return map_view; +			} +			@Override +			public void onDestroyView() { +				super.onDestroyView(); +				map_view = null; +			} +		}; +	} + +//	public void onActivityCreated() { +//		getChildFragmentManager().beginTransaction().add(R.id.map, mMapFragment).commit(); +//	} + +	private double pixel_distance(LatLng a, LatLng b) { +		Projection projection = mMap.getProjection(); + +		Point	a_pt = projection.toScreenLocation(a); +		Point	b_pt = projection.toScreenLocation(b); + +		return Math.hypot((double) (a_pt.x - b_pt.x), (double) (a_pt.y - b_pt.y)); +	} + +	private RocketOnline[] sorted_rockets() { +		RocketOnline[]	rocket_array = rockets.values().toArray(new RocketOnline[0]); + +		Arrays.sort(rocket_array); +		return rocket_array; +	} + +	public void onMapClick(LatLng lat_lng) { +		ArrayList<Integer>	near = new ArrayList<Integer>(); + +		for (RocketOnline rocket : sorted_rockets()) { +			LatLng	pos = rocket.marker.getPosition(); + +			if (pos == null) +				continue; + +			double distance = pixel_distance(lat_lng, pos); +			if (distance < rocket.size * 2) +				near.add(rocket.serial); +		} + +		if (near.size() != 0) +			altos_droid.touch_trackers(near.toArray(new Integer[0])); +	} + +	public boolean onMarkerClick(Marker marker) { +		onMapClick(marker.getPosition()); +		return true; +	} + +	public void setupMap(int map_type) { +		mMap = mMapFragment.getMap(); +		if (mMap != null) { +			set_map_type(map_type); +			mMap.setMyLocationEnabled(true); +			mMap.getUiSettings().setTiltGesturesEnabled(false); +			mMap.getUiSettings().setZoomControlsEnabled(false); +			mMap.setOnMarkerClickListener(this); +			mMap.setOnMapClickListener(this); + +			mPadMarker = mMap.addMarker( +					new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.drawable.pad)) +					                   .position(new LatLng(0,0)) +					                   .visible(false) +					); + +			mPolyline = mMap.addPolyline( +					new PolylineOptions().add(new LatLng(0,0), new LatLng(0,0)) +					                     .width(20) +					                     .color(Color.BLUE) +					                     .visible(false) +					); + +			mapLoaded = true; +		} +	} + +	public void center(double lat, double lon, double accuracy) { +		if (mMap == null) +			return; + +		if (mapAccuracy < 0 || accuracy < mapAccuracy/10) { +			mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(lat, lon),14)); +			mapAccuracy = accuracy; +		} +	} + +	private void set_rocket(int serial, AltosState state) { +		RocketOnline	rocket; + +		if (state.gps == null || state.gps.lat == AltosLib.MISSING) +			return; + +		if (mMap == null) +			return; + +		if (rockets.containsKey(serial)) { +			rocket = rockets.get(serial); +			rocket.set_position(new AltosLatLon(state.gps.lat, state.gps.lon), state.received_time); +		} else { +			rocket = new RocketOnline(context, +						  serial, +						  mMap, state.gps.lat, state.gps.lon, +						  state.received_time); +			rockets.put(serial, rocket); +		} +	} + +	private void remove_rocket(int serial) { +		RocketOnline rocket = rockets.get(serial); +		rocket.remove(); +		rockets.remove(serial); +	} + +	public void set_visible(boolean visible) { +		if (map_view == null) +			return; +		if (visible) +			map_view.setVisibility(View.VISIBLE); +		else +			map_view.setVisibility(View.GONE); +	} + +	public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) { + +		if (telem_state != null) { +			for (int serial : rockets.keySet()) { +				if (!telem_state.states.containsKey(serial)) +					remove_rocket(serial); +			} + +			for (int serial : telem_state.states.keySet()) { +				set_rocket(serial, telem_state.states.get(serial)); +			} +		} + +		if (state != null) { +			if (mapLoaded) { +				if (!pad_set && state.pad_lat != AltosLib.MISSING) { +					pad_set = true; +					mPadMarker.setPosition(new LatLng(state.pad_lat, state.pad_lon)); +					mPadMarker.setVisible(true); +				} +			} +			if (state.gps != null) { + +				target_position = new AltosLatLon(state.gps.lat, state.gps.lon); +				if (state.gps.locked && state.gps.nsat >= 4) +					center (state.gps.lat, state.gps.lon, 10); +			} +		} + +		if (receiver != null) { +			double accuracy; + +			if (receiver.hasAccuracy()) +				accuracy = receiver.getAccuracy(); +			else +				accuracy = 1000; + +			my_position = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude()); +			center (my_position.lat, my_position.lon, accuracy); +		} + +		if (my_position != null && target_position != null && mPolyline != null) { +			mPolyline.setPoints(Arrays.asList(new LatLng(my_position.lat, my_position.lon), new LatLng(target_position.lat, target_position.lon))); +			mPolyline.setVisible(true); +		} + +	} + +	public void set_map_type(int map_type) { +		if (mMap != null) { +			if (map_type == AltosMap.maptype_hybrid) +				mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); +			else if (map_type == AltosMap.maptype_satellite) +				mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE); +			else if (map_type == AltosMap.maptype_terrain) +				mMap.setMapType(GoogleMap.MAP_TYPE_TERRAIN); +			else +				mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL); +		} +	} + +	public AltosMapOnline(Context context) { +		this.context = context; +	} +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosUsb.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosUsb.java new file mode 100644 index 00000000..b7eb76a5 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosUsb.java @@ -0,0 +1,230 @@ +/* + * Copyright © 2015 Keith Packard <keithp@keithp.com> + * + * 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 java.util.HashMap; + +import android.content.Context; +import android.hardware.usb.*; +import android.app.*; +import android.os.Handler; + +import org.altusmetrum.altoslib_8.*; + +public class AltosUsb extends AltosDroidLink { + +	private Thread           input_thread   = null; + +	private Handler          handler; + +	private UsbManager		manager; +	private UsbDevice		device; +	private UsbDeviceConnection	connection; +	private UsbInterface 		iface; +	private UsbEndpoint		in, out; + +	private InputStream      input; +	private OutputStream     output; + +	// Constructor +	public AltosUsb(Context context, UsbDevice device, Handler handler) { +		super(handler); +//		set_debug(D); +		this.handler = handler; + +		iface = null; +		in = null; +		out = null; + +		int	niface = device.getInterfaceCount(); + +		for (int i = 0; i < niface; i++) { + +			iface = device.getInterface(i); + +			in = null; +			out = null; + +			int nendpoints = iface.getEndpointCount(); + +			for (int e = 0; e < nendpoints; e++) { +				UsbEndpoint	endpoint = iface.getEndpoint(e); + +				if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { +					switch (endpoint.getDirection()) { +					case UsbConstants.USB_DIR_OUT: +						out = endpoint; +						break; +					case UsbConstants.USB_DIR_IN: +						in = endpoint; +						break; +					} +				} +			} + +			if (in != null && out != null) +				break; +		} + +		if (in != null && out != null) { +			AltosDebug.debug("\tin %s out %s\n", in.toString(), out.toString()); + +			manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); + +			if (manager == null) { +				AltosDebug.debug("USB_SERVICE failed"); +				return; +			} + +			connection = manager.openDevice(device); + +			if (connection == null) { +				AltosDebug.debug("openDevice failed"); +				return; +			} + +			connection.claimInterface(iface, true); + +			input_thread = new Thread(this); +			input_thread.start(); + +			// Configure the newly connected device for telemetry +			print("~\nE 0\n"); +			set_monitor(false); +		} +	} + +	static private boolean isAltusMetrum(UsbDevice device) { +		if (device.getVendorId() != AltosLib.vendor_altusmetrum) +			return false; +		if (device.getProductId() < AltosLib.product_altusmetrum_min) +			return false; +		if (device.getProductId() > AltosLib.product_altusmetrum_max) +			return false; +		return true; +	} + +	static boolean matchProduct(int want_product, UsbDevice device) { + +		if (!isAltusMetrum(device)) +			return false; + +		if (want_product == AltosLib.product_any) +			return true; + +		int have_product = device.getProductId(); + +		if (want_product == AltosLib.product_basestation) +			return have_product == AltosLib.product_teledongle || +				have_product == AltosLib.product_teleterra || +				have_product == AltosLib.product_telebt || +				have_product == AltosLib.product_megadongle; + +		if (want_product == AltosLib.product_altimeter) +			return have_product == AltosLib.product_telemetrum || +				have_product == AltosLib.product_telemega || +				have_product == AltosLib.product_easymega || +				have_product == AltosLib.product_telegps || +				have_product == AltosLib.product_easymini || +				have_product == AltosLib.product_telemini; + +		if (have_product == AltosLib.product_altusmetrum)	/* old devices match any request */ +			return true; + +		if (want_product == have_product) +			return true; + +		return false; +	} + +	static public boolean request_permission(Context context, UsbDevice device, PendingIntent pi) { +		UsbManager	manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); + +//		if (manager.hasPermission(device)) +//			return true; + +		AltosDebug.debug("request permission for USB device " + device.toString()); + +		manager.requestPermission(device, pi); +		return false; +	} + +	static public UsbDevice find_device(Context context, int match_product) { +		UsbManager	manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); + +		HashMap<String,UsbDevice>	devices = manager.getDeviceList(); + +		for (UsbDevice	device : devices.values()) { +			int	vendor = device.getVendorId(); +			int	product = device.getProductId(); + +			if (matchProduct(match_product, device)) { +				AltosDebug.debug("found USB device " + device.toString()); +				return device; +			} +		} + +		return null; +	} + +	private void disconnected() { +		if (closed()) { +			AltosDebug.debug("disconnected after closed"); +			return; +		} + +		AltosDebug.debug("Sending disconnected message"); +		handler.obtainMessage(TelemetryService.MSG_DISCONNECTED, this).sendToTarget(); +	} + +	void close_device() { +		UsbDeviceConnection	tmp_connection; + +		synchronized(this) { +			tmp_connection = connection; +			connection = null; +		} + +		if (tmp_connection != null) { +			AltosDebug.debug("Closing USB device"); +			tmp_connection.close(); +		} +	} + +	int read(byte[] buffer, int len) { +		int ret = connection.bulkTransfer(in, buffer, len, -1); +		AltosDebug.debug("read(%d) = %d\n", len, ret); +		return ret; +	} + +	int write(byte[] buffer, int len) { +		int ret = connection.bulkTransfer(out, buffer, len, -1); +		AltosDebug.debug("write(%d) = %d\n", len, ret); +		return ret; +	} + +	// 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/AltosViewPager.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosViewPager.java index 223ae75a..f22298d7 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosViewPager.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosViewPager.java @@ -34,14 +34,19 @@ public class AltosViewPager extends ViewPager {      @Override      protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { -	if(v.getClass() != null && -	   v.getClass().getPackage() != null && -	   v.getClass().getPackage().getName() != null && -	   v.getClass().getPackage().getName().startsWith("maps.")) -	{ -            return true; -        } -        return super.canScroll(v, checkV, dx, x, y); + +	    if (v.getClass() != null && +		v.getClass().getName() != null && +		v.getClass().getName().endsWith("MapOffline")) +		    return true; + +	    if(v.getClass() != null && +	       v.getClass().getPackage() != null && +	       v.getClass().getPackage().getName() != null && +	       v.getClass().getPackage().getName().startsWith("maps.")) +		    return true; + +	    return super.canScroll(v, checkV, dx, x, y);      }  } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java index 2d32dc07..325b89d2 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java @@ -20,201 +20,305 @@ package org.altusmetrum.AltosDroid;  import android.speech.tts.TextToSpeech;  import android.speech.tts.TextToSpeech.OnInitListener; +import android.location.Location; -import org.altusmetrum.altoslib_6.*; +import org.altusmetrum.altoslib_8.*;  public class AltosVoice {  	private TextToSpeech tts         = null;  	private boolean      tts_enabled = false; -	private IdleThread   idle_thread = null; +	static final int TELL_MODE_NONE = 0; +	static final int TELL_MODE_PAD = 1; +	static final int TELL_MODE_FLIGHT = 2; +	static final int TELL_MODE_RECOVER = 3; -	private AltosState   old_state   = null; +	static final int TELL_FLIGHT_NONE = 0; +	static final int TELL_FLIGHT_STATE = 1; +	static final int TELL_FLIGHT_SPEED = 2; +	static final int TELL_FLIGHT_HEIGHT = 3; +	static final int TELL_FLIGHT_TRACK = 4; -	public AltosVoice(AltosDroid a) { +	private int		last_tell_mode; +	private int		last_tell_serial = AltosLib.MISSING; +	private int		last_state; +	private AltosGPS	last_gps; +	private double		last_height = AltosLib.MISSING; +	private Location	last_receiver; +	private long		last_speak_time; +	private int		last_flight_tell = TELL_FLIGHT_NONE; + +	private long now() { +		return System.currentTimeMillis(); +	} + +	private void reset_last() { +		last_tell_mode = TELL_MODE_NONE; +		last_speak_time = now() - 100 * 1000; +		last_gps = null; +		last_height = AltosLib.MISSING; +		last_receiver = null; +		last_state = AltosLib.ao_flight_invalid; +		last_flight_tell = TELL_FLIGHT_NONE; +	} +	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) { -					idle_thread = new IdleThread(); -				}  			}  		}); +		reset_last(); +	} +	public synchronized void set_enable(boolean enable) { +		tts_enabled = enable;  	}  	public synchronized void speak(String s) {  		if (!tts_enabled) return; +		last_speak_time = now();  		tts.speak(s, TextToSpeech.QUEUE_ADD, null);  	} +	public synchronized long time_since_speak() { +		return now() - last_speak_time; +	} + +	public synchronized void speak(String format, Object ... arguments) { +		speak(String.format(format, arguments)); +	} + +	public synchronized boolean is_speaking() { +		return tts.isSpeaking(); +	} +  	public void stop() { -		if (tts != null) tts.shutdown(); -		if (idle_thread != null) { -			idle_thread.interrupt(); -			idle_thread = null; +		if (tts != null) { +			tts.stop(); +			tts.shutdown();  		}  	} -	public void tell(AltosState state, AltosGreatCircle from_receiver) { -		if (!tts_enabled) return; +	private boolean		last_apogee_good; +	private boolean		last_main_good; +	private boolean		last_gps_good; -		boolean	spoke = false; -		if (old_state == null || old_state.state != state.state) { -			if (state.state != AltosLib.ao_flight_stateless) -				speak(state.state_name()); -			if ((old_state == null || old_state.state <= AltosLib.ao_flight_boost) && -			    state.state > AltosLib.ao_flight_boost) { -				if (state.max_speed() != AltosLib.MISSING) -					speak(String.format("Max speed: %s.", -							    AltosConvert.speed.say_units(state.max_speed()))); -				spoke = true; -			} else if ((old_state == null || old_state.state < AltosLib.ao_flight_drogue) && -			           state.state >= AltosLib.ao_flight_drogue) { -				if (state.max_height() != AltosLib.MISSING) -					speak(String.format("Max height: %s.", -							    AltosConvert.height.say_units(state.max_height()))); -				spoke = true; -			} +	private boolean tell_gonogo(String name, +				  boolean current, +				  boolean previous, +				  boolean new_mode) { +		if (current != previous || new_mode) +			speak("%s %s.", name, current ? "ready" : "not ready"); +		return current; +	} + +	private boolean tell_pad(TelemetryState telem_state, AltosState state, +			      AltosGreatCircle from_receiver, Location receiver) { + +		if (state == null) +			return false; + +		if (state.apogee_voltage != AltosLib.MISSING) +			last_apogee_good = tell_gonogo("apogee", +						       state.apogee_voltage >= AltosLib.ao_igniter_good, +						       last_apogee_good, +						       last_tell_mode != TELL_MODE_PAD); + +		if (state.main_voltage != AltosLib.MISSING) +			last_main_good = tell_gonogo("main", +						     state.main_voltage >= AltosLib.ao_igniter_good, +						     last_main_good, +						     last_tell_mode != TELL_MODE_PAD); + +		if (state.gps != null) +			last_gps_good = tell_gonogo("G P S", +						    state.gps_ready, +						    last_gps_good, +						    last_tell_mode != TELL_MODE_PAD); +		return true; +	} + + +	private boolean descending(int state) { +		return AltosLib.ao_flight_drogue <= state && state <= AltosLib.ao_flight_landed; +	} + +	private boolean target_moved(AltosState state) { +		if (last_gps != null && state != null && state.gps != null) { +			AltosGreatCircle	moved = new AltosGreatCircle(last_gps.lat, last_gps.lon, last_gps.alt, +									     state.gps.lat, state.gps.lon, state.gps.alt); +			double			height_change = 0; +			double			height = state.height(); + +			if (height != AltosLib.MISSING && last_height != AltosLib.MISSING) +				height_change = Math.abs(last_height - height); + +			if (moved.range < 10 && height_change < 10) +				return false;  		} -		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; -			} +		return true; +	} + +	private boolean receiver_moved(Location receiver) { +		if (last_receiver != null && receiver != null) { +			AltosGreatCircle	moved = new AltosGreatCircle(last_receiver.getLatitude(), +									     last_receiver.getLongitude(), +									     last_receiver.getAltitude(), +									     receiver.getLatitude(), +									     receiver.getLongitude(), +									     receiver.getAltitude()); +			if (moved.range < 10) +				return false;  		} -		old_state = state; -		if (idle_thread != null) -			idle_thread.notice(state, from_receiver, spoke); +		return true;  	} +	private boolean tell_flight(TelemetryState telem_state, AltosState state, +				    AltosGreatCircle from_receiver, Location receiver) { -	class IdleThread extends Thread { -		boolean	           started; -		private AltosState state; -		private AltosGreatCircle from_receiver; -		int                reported_landing; -		int                report_interval; -		long               report_time; +		boolean	spoken = false; -		public synchronized void report(boolean last) { -			if (state == null) -				return; +		if (state == null) +			return false; -			/* reset the landing count once we hear about a new flight */ -			if (state.state < AltosLib.ao_flight_drogue) -				reported_landing = 0; +		if (last_tell_mode != TELL_MODE_FLIGHT) +			last_flight_tell = TELL_FLIGHT_NONE; -			/* Shut up once the rocket is on the ground */ -			if (reported_landing > 2) { -				return; +		if (state.state != last_state && AltosLib.ao_flight_boost <= state.state && state.state <= AltosLib.ao_flight_landed) { +			speak(state.state_name()); +			if (descending(state.state) && !descending(last_state)) { +				if (state.max_height() != AltosLib.MISSING) { +					speak("max height: %s.", +					      AltosConvert.height.say_units(state.max_height())); +				}  			} +			last_flight_tell = TELL_FLIGHT_STATE; +			return true; +		} -			/* If the rocket isn't on the pad, then report location */ -			if ((AltosLib.ao_flight_drogue <= state.state && -			      state.state < AltosLib.ao_flight_landed) || -			     state.state == AltosLib.ao_flight_stateless) -			{ -				AltosGreatCircle	position; - -				if (from_receiver != null) -					position = from_receiver; -				else -					position = state.from_pad; - -				if (position != null) { -					speak(String.format("Height %s, bearing %s %d, elevation %d, range %s.\n", -							    AltosConvert.height.say_units(state.height()), -							    position.bearing_words( -								    AltosGreatCircle.BEARING_VOICE), -							    (int) (position.bearing + 0.5), -							    (int) (position.elevation + 0.5), -							    AltosConvert.distance.say_units(position.range))); -				} -			} else if (state.state > AltosLib.ao_flight_pad) { -				if (state.height() != AltosLib.MISSING) -					speak(AltosConvert.height.say_units(state.height())); +		if (last_tell_mode == TELL_MODE_FLIGHT && last_flight_tell == TELL_FLIGHT_TRACK) { +			if (time_since_speak() < 10 * 1000) +				return false; +			if (!target_moved(state) && !receiver_moved(receiver)) +				return false; +		} + +		double	speed; +		double	height; + +		if (last_flight_tell == TELL_FLIGHT_NONE || last_flight_tell == TELL_FLIGHT_STATE || last_flight_tell == TELL_FLIGHT_TRACK) { +			last_flight_tell = TELL_FLIGHT_SPEED; + +			if (state.state <= AltosLib.ao_flight_coast) { +				speed = state.speed();  			} else { -				reported_landing = 0; +				speed = state.gps_speed(); +				if (speed == AltosLib.MISSING) +					speed = state.speed();  			} -			/* 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.received_time >= 15000 || -			     state.state == AltosLib.ao_flight_landed)) -			{ -				if (Math.abs(state.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 %s.", -					                    (int) (state.from_pad.bearing + 0.5), -							    AltosConvert.distance.say_units(state.from_pad.distance))); -				++reported_landing; +			if (speed != AltosLib.MISSING) { +				speak("speed: %s.", AltosConvert.speed.say_units(speed)); +				return true;  			}  		} -		long now () { -			return System.currentTimeMillis(); -		} +		if (last_flight_tell == TELL_FLIGHT_SPEED) { +			last_flight_tell = TELL_FLIGHT_HEIGHT; +			height = state.height(); -		void set_report_time() { -			report_time = now() + report_interval; +			if (height != AltosLib.MISSING) { +				speak("height: %s.", AltosConvert.height.say_units(height)); +				return true; +			}  		} -		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) { +		if (last_flight_tell == TELL_FLIGHT_HEIGHT) { +			last_flight_tell = TELL_FLIGHT_TRACK; +			if (from_receiver != null) { +				speak("bearing %s %d, elevation %d, range %s.", +				      from_receiver.bearing_words( +					      AltosGreatCircle.BEARING_VOICE), +				      (int) (from_receiver.bearing + 0.5), +				      (int) (from_receiver.elevation + 0.5), +				      AltosConvert.distance.say(from_receiver.range)); +				return true;  			}  		} -		public synchronized void notice(AltosState new_state, AltosGreatCircle new_from_receiver, boolean spoken) { -			AltosState old_state = state; -			state = new_state; -			from_receiver = new_from_receiver; -			if (!started && state.state > AltosLib.ao_flight_pad) { -				started = true; -				start(); -			} +		return spoken; +	} -			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(); -		} +	private boolean tell_recover(TelemetryState telem_state, AltosState state, +				     AltosGreatCircle from_receiver, Location receiver) { + +		if (from_receiver == null) +			return false; -		public IdleThread() { -			state = null; -			reported_landing = 0; -			report_interval = 10000; +		if (last_tell_mode == TELL_MODE_RECOVER) { +			if (!target_moved(state) && !receiver_moved(receiver)) +				return false; +			if (time_since_speak() <= 10 * 1000) +				return false;  		} + +		String direction = AltosDroid.direction(from_receiver, receiver); +		if (direction == null) +			direction = String.format("Bearing %d", (int) (from_receiver.bearing + 0.5)); + +		speak("%s, range %s.", direction, +		      AltosConvert.distance.say_units(from_receiver.distance)); + +		return true;  	} +	public void tell(TelemetryState telem_state, AltosState state, +			 AltosGreatCircle from_receiver, Location receiver, +			 AltosDroidTab tab) { + +		boolean	spoken = false; + +		if (!tts_enabled) return; + +		if (is_speaking()) return; + +		int	tell_serial = last_tell_serial; + +		if (state != null) +			tell_serial = state.serial; + +		if (tell_serial != last_tell_serial) +			reset_last(); + +		int	tell_mode = TELL_MODE_NONE; + +		if (tab.tab_name().equals(AltosDroid.tab_pad_name)) +			tell_mode = TELL_MODE_PAD; +		else if (tab.tab_name().equals(AltosDroid.tab_flight_name)) +			tell_mode = TELL_MODE_FLIGHT; +		else +			tell_mode = TELL_MODE_RECOVER; + +		if (tell_mode == TELL_MODE_PAD) +			spoken = tell_pad(telem_state, state, from_receiver, receiver); +		else if (tell_mode == TELL_MODE_FLIGHT) +			spoken = tell_flight(telem_state, state, from_receiver, receiver); +		else +			spoken = tell_recover(telem_state, state, from_receiver, receiver); + +		if (spoken) { +			last_tell_mode = tell_mode; +			last_tell_serial = tell_serial; +			if (state != null) { +				last_state = state.state; +				last_height = state.height(); +				if (state.gps != null) +					last_gps = state.gps; +			} +			if (receiver != null) +				last_receiver = receiver; +		} +	}  } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/DeviceAddress.java b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceAddress.java new file mode 100644 index 00000000..673d72dd --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceAddress.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2015 Keith Packard <keithp@keithp.com> + * + * 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; + +public class DeviceAddress { +	public String	address; +	public String	name; + +	public DeviceAddress(String address, String name) { +		this.address = address; +		this.name = name; +	} +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java index 71692122..f36ef267 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java @@ -27,7 +27,6 @@ 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; @@ -45,12 +44,10 @@ import android.widget.AdapterView.OnItemClickListener;   * 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"; +	public static final String EXTRA_DEVICE_ADDRESS = "device_address"; +	public static final String EXTRA_DEVICE_NAME = "device_name";  	// Member fields  	private BluetoothAdapter mBtAdapter; @@ -136,7 +133,7 @@ public class DeviceListActivity extends Activity {  	* Start device discover with the BluetoothAdapter  	*/  	private void doDiscovery() { -		if (D) Log.d(TAG, "doDiscovery()"); +		AltosDebug.debug("doDiscovery()");  		// Indicate scanning in the title  		setProgressBarIndeterminateVisibility(true); @@ -164,9 +161,20 @@ public class DeviceListActivity extends Activity {  			String info = ((TextView) v).getText().toString();  			String address = info.substring(info.length() - 17); +			int newline = info.indexOf('\n'); + +			String name = null; +			if (newline > 0) +				name = info.substring(0, newline); +			else +				name = info; + +			AltosDebug.debug("******* selected item '%s'", info); +  			// Create the result Intent and include the MAC address  			Intent intent = new Intent();  			intent.putExtra(EXTRA_DEVICE_ADDRESS, address); +			intent.putExtra(EXTRA_DEVICE_NAME, name);  			// Set result and finish this Activity  			setResult(Activity.RESULT_OK, intent); @@ -183,14 +191,22 @@ public class DeviceListActivity extends Activity {  			// When discovery finds a device  			if (BluetoothDevice.ACTION_FOUND.equals(action)) { -				// Get the BluetoothDevice object from the Intent + +				/* 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 -				    && device.getName().startsWith("TeleBT")               ) { -					mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); + +				/* If it's already paired, skip it, because it's been listed already +				 */ +				if (device != null && device.getBondState() != BluetoothDevice.BOND_BONDED) +				{ +					String	name = device.getName(); +					if (name != null && name.startsWith("TeleBT")) +						mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());  				} -			// When discovery is finished, change the Activity title + +			/* When discovery is finished, change the Activity title +			 */  			} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {  				setProgressBarIndeterminateVisibility(false);  				setTitle(R.string.select_device); diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java b/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java index 267c90f8..6cecbdf1 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java @@ -52,27 +52,14 @@ public class GoNoGoLights {  		missing = m;  		set = true;  		if (missing) { -			hide();  			red.setImageDrawable(dGray);  			green.setImageDrawable(dGray);  		} else if (state) {  			red.setImageDrawable(dGray);  			green.setImageDrawable(dGreen); -			show();  		} else {  			red.setImageDrawable(dRed);  			green.setImageDrawable(dGray); -			show();  		}  	} - -	public void show() { -		red.setVisibility(View.VISIBLE); -		green.setVisibility(View.VISIBLE); -	} - -	public void hide() { -		red.setVisibility(View.GONE); -		green.setVisibility(View.GONE); -	}  } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/MapTypeActivity.java b/altosdroid/src/org/altusmetrum/AltosDroid/MapTypeActivity.java new file mode 100644 index 00000000..8846e56c --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/MapTypeActivity.java @@ -0,0 +1,84 @@ +/* + * Copyright © 2015 Keith Packard <keithp@keithp.com> + * + * 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.util.*; +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.view.View; +import android.view.Window; +import android.view.View.OnClickListener; +import android.widget.*; +import android.widget.AdapterView.*; + +import org.altusmetrum.altoslib_8.*; + +public class MapTypeActivity extends Activity { +	private Button hybrid; +	private Button satellite; +	private Button roadmap; +	private Button terrain; +	private int selected_type; + +	public static final String EXTRA_MAP_TYPE = "map_type"; + +	private void done(int type) { + +		Intent intent = new Intent(); +		intent.putExtra(EXTRA_MAP_TYPE, type); +		setResult(Activity.RESULT_OK, intent); +		finish(); +	} + +	public void selectType(View view) { +		AltosDebug.debug("selectType %s", view.toString()); +		if (view == hybrid) +			done(AltosMap.maptype_hybrid); +		if (view == satellite) +			done(AltosMap.maptype_satellite); +		if (view == roadmap) +			done(AltosMap.maptype_roadmap); +		if (view == terrain) +			done(AltosMap.maptype_terrain); +	} + +	@Override +	protected void onCreate(Bundle savedInstanceState) { +		super.onCreate(savedInstanceState); + +		// Setup the window +		requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); +		setContentView(R.layout.map_type); + +		hybrid = (Button) findViewById(R.id.map_type_hybrid); +		satellite = (Button) findViewById(R.id.map_type_satellite); +		roadmap = (Button) findViewById(R.id.map_type_roadmap); +		terrain = (Button) findViewById(R.id.map_type_terrain); + +		// Set result CANCELED incase the user backs out +		setResult(Activity.RESULT_CANCELED); +	} +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/PreloadMapActivity.java b/altosdroid/src/org/altusmetrum/AltosDroid/PreloadMapActivity.java new file mode 100644 index 00000000..d7462089 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/PreloadMapActivity.java @@ -0,0 +1,418 @@ +/* + * Copyright © 2015 Keith Packard <keithp@keithp.com> + * + * 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.util.*; +import java.io.*; +import java.text.*; + +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.view.View; +import android.view.Window; +import android.view.View.OnClickListener; +import android.widget.*; +import android.widget.AdapterView.*; +import android.location.Location; +import android.location.LocationManager; +import android.location.LocationListener; +import android.location.Criteria; + +import org.altusmetrum.altoslib_8.*; + +/** + * 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 PreloadMapActivity extends Activity implements AltosLaunchSiteListener, AltosMapInterface, AltosMapLoaderListener, LocationListener { + +	private ArrayAdapter<AltosLaunchSite> known_sites_adapter; + +	private CheckBox	hybrid; +	private CheckBox	satellite; +	private CheckBox	roadmap; +	private CheckBox	terrain; + +	private Spinner		known_sites_spinner; +	private Spinner		min_zoom; +	private Spinner		max_zoom; +	private TextView	radius_label; +	private Spinner		radius; + +	private EditText	latitude; +	private EditText	longitude; + +	private ProgressBar	progress; + +	/* AltosMapLoaderListener interfaces */ +	public void loader_start(final int max) { +		this.runOnUiThread(new Runnable() { +				public void run() { +					progress.setMax(max); +					progress.setProgress(0); +				} +			}); +	} + +	public void loader_notify(final int cur, final int max, final String name) { +		this.runOnUiThread(new Runnable() { +				public void run() { +					progress.setProgress(cur); +				} +			}); +	} + +	public void loader_done(int max) { +		this.runOnUiThread(new Runnable() { +				public void run() { +					progress.setProgress(0); +					finish(); +				} +			}); +	} + +	/* AltosLaunchSiteListener interface */ +	public void notify_launch_sites(final List<AltosLaunchSite> sites) { +		this.runOnUiThread(new Runnable() { +				public void run() { +					for (AltosLaunchSite site : sites) +						known_sites_adapter.add(site); +				} +			}); +	} + +	AltosMap	map; +	AltosMapLoader	loader; + +	class PreloadMapImage implements AltosImage { +		public void flush() { +		} + +		public PreloadMapImage(File file) { +		} +	} + +	public AltosMapPath new_path() { +		return null; +	} + +	public AltosMapLine new_line() { +		return null; +	} + +	public AltosImage load_image(File file) throws Exception { +		return new PreloadMapImage(file); +	} + +	public AltosMapMark new_mark(double lat, double lon, int state) { +		return null; +	} + +	class PreloadMapTile extends AltosMapTile { +		public void paint(AltosMapTransform t) { +		} + +		public PreloadMapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) { +			super(listener, upper_left, center, zoom, maptype, px_size, 2); +		} + +	} + +	public AltosMapTile new_tile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) { +		return new PreloadMapTile(listener, upper_left, center, zoom, maptype, px_size); +	} + +	public int width() { +		return AltosMap.px_size; +	} + +	public int height() { +		return AltosMap.px_size; +	} + +	public void repaint() { +	} + +	public void repaint(AltosRectangle damage) { +	} + +	public void set_zoom_label(String label) { +	} + +	public void select_object(AltosLatLon latlon) { +	} + +	public void debug(String format, Object ... arguments) { +		AltosDebug.debug(format, arguments); +	} + +	/* LocationProvider interface */ + +	AltosLaunchSite	current_location_site; + +	public void onLocationChanged(Location location) { +		AltosDebug.debug("location changed"); +		if (current_location_site == null) { +			AltosLaunchSite selected_item = (AltosLaunchSite) known_sites_spinner.getSelectedItem(); + +			current_location_site = new AltosLaunchSite("Current Location", location.getLatitude(), location.getLongitude()); +			known_sites_adapter.insert(current_location_site, 0); + +			if (selected_item != null) +				known_sites_spinner.setSelection(known_sites_adapter.getPosition(selected_item)); +			else { +				latitude.setText(new StringBuffer(String.format("%12.6f", current_location_site.latitude))); +				longitude.setText(new StringBuffer(String.format("%12.6f", current_location_site.longitude))); +			} +		} else { +			current_location_site.latitude = location.getLatitude(); +			current_location_site.longitude = location.getLongitude(); +		} +	} + +	public void onStatusChanged(String provider, int status, Bundle extras) { +	} + +	public void onProviderEnabled(String provider) { +	} + +	public void onProviderDisabled(String provider) { +	} + +	private double text(EditText view) throws ParseException { +		return AltosParse.parse_double_locale(view.getEditableText().toString()); +	} + +	private double latitude() throws ParseException { +		return text(latitude); +	} + +	private double longitude() throws ParseException { +		return text(longitude); +	} + +	private int value(Spinner spinner) { +		return (Integer) spinner.getSelectedItem(); +	} + +	private int min_z() { +		return value(min_zoom); +	} + +	private int max_z() { +		return value(max_zoom); +	} + +	private double value_distance(Spinner spinner) { +		return (Double) spinner.getSelectedItem(); +	} + +	private double radius() { +		double r = value_distance(radius); +		if (AltosPreferences.imperial_units()) +			r = AltosConvert.distance.inverse(r); +		else +			r = r * 1000; +		return r; +	} + +	private int bit(CheckBox box, int value) { +		if (box.isChecked()) +			return 1 << value; +		return 0; +	} + +	private int types() { +		return (bit(hybrid, AltosMap.maptype_hybrid) | +			bit(satellite, AltosMap.maptype_satellite) | +			bit(roadmap, AltosMap.maptype_roadmap) | +			bit(terrain, AltosMap.maptype_terrain)); +	} + +	private void load() { +		try { +			double	lat = latitude(); +			double	lon = longitude(); +			int	min = min_z(); +			int	max = max_z(); +			double	r = radius(); +			int	t = types(); + +			AltosDebug.debug("PreloadMap load %f %f %d %d %f %d\n", +					 lat, lon, min, max, r, t); +			loader.load(lat, lon, min, max, r, t); +		} catch (ParseException e) { +			AltosDebug.debug("PreloadMap load raised exception %s", e.toString()); +		} +	} + +	private void add_numbers(Spinner spinner, int min, int max, int def) { + +		ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(this, android.R.layout.simple_spinner_item); + +		int	spinner_def = 0; +		int	pos = 0; + +		for (int i = min; i <= max; i++) { +			adapter.add(new Integer(i)); +			if (i == def) +				spinner_def = pos; +			pos++; +		} + +		spinner.setAdapter(adapter); +		spinner.setSelection(spinner_def); +	} + + +	private void add_distance(Spinner spinner, double[] distances_km, double def_km, double[] distances_mi, double def_mi) { + +		ArrayAdapter<Double> adapter = new ArrayAdapter<Double>(this, android.R.layout.simple_spinner_item); + +		int	spinner_def = 0; +		int	pos = 0; + +		double[] distances; +		double	def; +		if (AltosPreferences.imperial_units()) { +			distances = distances_mi; +			def = def_mi; +		} else { +			distances = distances_km; +			def = def_km; +		} + +		for (int i = 0; i < distances.length; i++) { +			adapter.add(distances[i]); +			if (distances[i] == def) +				spinner_def = pos; +			pos++; +		} + +		spinner.setAdapter(adapter); +		spinner.setSelection(spinner_def); +	} + + + +	class SiteListListener implements OnItemSelectedListener { +		public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) { +			AltosLaunchSite	site = (AltosLaunchSite) parent.getItemAtPosition(pos); +			latitude.setText(new StringBuffer(String.format("%12.6f", site.latitude))); +			longitude.setText(new StringBuffer(String.format("%12.6f", site.longitude))); +		} +		public void onNothingSelected(AdapterView<?> parent) { +		} + +		public SiteListListener() { +		} +	} + +	double[]	radius_mi = { 1, 2, 5, 10, 20 }; +	double		radius_def_mi = 2; +	double[]	radius_km = { 1, 2, 5, 10, 20, 30 }; +	double		radius_def_km = 2; + +	@Override +	protected void onCreate(Bundle savedInstanceState) { +		super.onCreate(savedInstanceState); + +		// Setup the window +		requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); +		setContentView(R.layout.map_preload); + +		// Set result CANCELED incase the user backs out +		setResult(Activity.RESULT_CANCELED); + +		// Initialize the button to perform device discovery +		Button loadButton = (Button) findViewById(R.id.preload_load); +		loadButton.setOnClickListener(new OnClickListener() { +			public void onClick(View v) { +				load(); +			} +		}); + +		latitude = (EditText) findViewById(R.id.preload_latitude); +		longitude = (EditText) findViewById(R.id.preload_longitude); + +		hybrid = (CheckBox) findViewById(R.id.preload_hybrid); +		satellite = (CheckBox) findViewById(R.id.preload_satellite); +		roadmap = (CheckBox) findViewById(R.id.preload_roadmap); +		terrain = (CheckBox) findViewById(R.id.preload_terrain); + +		hybrid.setChecked(true); + +		min_zoom = (Spinner) findViewById(R.id.preload_min_zoom); +		add_numbers(min_zoom, +			    AltosMap.min_zoom - AltosMap.default_zoom, +			    AltosMap.max_zoom - AltosMap.default_zoom, -2); +		max_zoom = (Spinner) findViewById(R.id.preload_max_zoom); +		add_numbers(max_zoom, +			    AltosMap.min_zoom - AltosMap.default_zoom, +			    AltosMap.max_zoom - AltosMap.default_zoom, 2); +		radius_label = (TextView) findViewById(R.id.preload_radius_label); +		radius = (Spinner) findViewById(R.id.preload_radius); +		if (AltosPreferences.imperial_units()) +			radius_label.setText("Radius (miles)"); +		else +			radius_label.setText("Radius (km)"); +		add_distance(radius, radius_km, radius_def_km, radius_mi, radius_def_mi); + +		progress = (ProgressBar) findViewById(R.id.preload_progress); + +		// Initialize array adapters. One for already paired devices and +		// one for newly discovered devices +		known_sites_spinner = (Spinner) findViewById(R.id.preload_site_list); + +		known_sites_adapter = new ArrayAdapter<AltosLaunchSite>(this, android.R.layout.simple_spinner_item); + +		known_sites_adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + +		known_sites_spinner.setAdapter(known_sites_adapter); +		known_sites_spinner.setOnItemSelectedListener(new SiteListListener()); + +		map = new AltosMap(this); + +		loader = new AltosMapLoader(map, this); + +		// Listen for GPS and Network position updates +		LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); + +		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this); + +		new AltosLaunchSites(this); +	} + +	@Override +	protected void onDestroy() { +		super.onDestroy(); + +		// Stop listening for location updates +		((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this); +	} +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java deleted file mode 100644 index 23de9622..00000000 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright © 2013 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 org.altusmetrum.altoslib_6.*; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import android.location.Location; - -public class TabAscent extends AltosDroidTab { -	AltosDroid mAltosDroid; - -	private TextView mHeightView; -	private TextView mMaxHeightView; -	private TextView mSpeedView; -	private TextView mMaxSpeedView; -	private TextView mAccelView; -	private TextView mMaxAccelView; -	private TextView mLatitudeView; -	private TextView mLongitudeView; -	private TextView mApogeeVoltageView; -	private GoNoGoLights mApogeeLights; -	private TextView mMainVoltageView; -	private GoNoGoLights mMainLights; - -	@Override -	public void onAttach(Activity activity) { -		super.onAttach(activity); -		mAltosDroid = (AltosDroid) activity; -		mAltosDroid.registerTab(this); -	} - -	@Override -	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { -		View v = inflater.inflate(R.layout.tab_ascent, container, false); - -		mHeightView    = (TextView) v.findViewById(R.id.height_value); -		mMaxHeightView = (TextView) v.findViewById(R.id.max_height_value); -		mSpeedView     = (TextView) v.findViewById(R.id.speed_value); -		mMaxSpeedView  = (TextView) v.findViewById(R.id.max_speed_value); -		mAccelView     = (TextView) v.findViewById(R.id.accel_value); -		mMaxAccelView  = (TextView) v.findViewById(R.id.max_accel_value); -		mLatitudeView  = (TextView) v.findViewById(R.id.lat_value); -		mLongitudeView = (TextView) v.findViewById(R.id.lon_value); - -		mApogeeVoltageView = (TextView) v.findViewById(R.id.apogee_voltage_value); -		mApogeeLights = new GoNoGoLights((ImageView) v.findViewById(R.id.apogee_redled), -		                                 (ImageView) v.findViewById(R.id.apogee_greenled), -		                                 getResources()); - -		mMainVoltageView = (TextView) v.findViewById(R.id.main_voltage_value); -		mMainLights = new GoNoGoLights((ImageView) v.findViewById(R.id.main_redled), -		                               (ImageView) v.findViewById(R.id.main_greenled), -		                               getResources()); - -		return v; -	} - -	@Override -	public void onDestroy() { -		super.onDestroy(); -		mAltosDroid.unregisterTab(this); -		mAltosDroid = null; -	} - -	public String tab_name() { -		return "ascent"; -	} - -	public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) { -		if (state != null) { -			set_value(mHeightView, AltosConvert.height, 6, state.height()); -			set_value(mHeightView, AltosConvert.height, 6, state.height()); -			set_value(mMaxHeightView, AltosConvert.height, 6, state.max_height()); -			set_value(mSpeedView, AltosConvert.speed, 6, state.speed()); -			set_value(mMaxSpeedView, AltosConvert.speed, 6, state.max_speed()); -			set_value(mAccelView, AltosConvert.accel, 6, state.acceleration()); -			set_value(mMaxAccelView, AltosConvert.accel, 6, state.max_acceleration()); - -			if (state.gps != null) { -				mLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S")); -				mLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W")); -			} else { -				mLatitudeView.setText(""); -				mLongitudeView.setText(""); -			} - -			mApogeeVoltageView.setText(AltosDroid.number("%4.2f V", state.apogee_voltage)); -			mApogeeLights.set(state.apogee_voltage > 3.2, state.apogee_voltage == AltosLib.MISSING); - -			mMainVoltageView.setText(AltosDroid.number("%4.2f V", state.main_voltage)); -			mMainLights.set(state.main_voltage > 3.2, state.main_voltage == AltosLib.MISSING); -		} -	} -} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java deleted file mode 100644 index 4ec6f409..00000000 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright © 2013 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 org.altusmetrum.altoslib_6.*; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import android.location.Location; - -public class TabDescent extends AltosDroidTab { -	AltosDroid mAltosDroid; - -	private TextView mSpeedView; -	private TextView mHeightView; -	private TextView mElevationView; -	private TextView mRangeView; -	private TextView mBearingView; -	private TextView mCompassView; -	private TextView mDistanceView; -	private TextView mLatitudeView; -	private TextView mLongitudeView; -	private TextView mApogeeVoltageView; -	private GoNoGoLights mApogeeLights; -	private TextView mMainVoltageView; -	private GoNoGoLights mMainLights; - - -	@Override -	public void onAttach(Activity activity) { -		super.onAttach(activity); -		mAltosDroid = (AltosDroid) activity; -		mAltosDroid.registerTab(this); -	} - -	@Override -	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { -		View v = inflater.inflate(R.layout.tab_descent, container, false); - -		mSpeedView     = (TextView) v.findViewById(R.id.speed_value); -		mHeightView    = (TextView) v.findViewById(R.id.height_value); -		mElevationView = (TextView) v.findViewById(R.id.elevation_value); -		mRangeView     = (TextView) v.findViewById(R.id.range_value); -		mBearingView   = (TextView) v.findViewById(R.id.bearing_value); -		mCompassView   = (TextView) v.findViewById(R.id.compass_value); -		mDistanceView  = (TextView) v.findViewById(R.id.distance_value); -		mLatitudeView  = (TextView) v.findViewById(R.id.lat_value); -		mLongitudeView = (TextView) v.findViewById(R.id.lon_value); - -		mApogeeVoltageView = (TextView) v.findViewById(R.id.apogee_voltage_value); -		mApogeeLights = new GoNoGoLights((ImageView) v.findViewById(R.id.apogee_redled), -		                                 (ImageView) v.findViewById(R.id.apogee_greenled), -		                                 getResources()); - -		mMainVoltageView = (TextView) v.findViewById(R.id.main_voltage_value); -		mMainLights = new GoNoGoLights((ImageView) v.findViewById(R.id.main_redled), -		                               (ImageView) v.findViewById(R.id.main_greenled), -		                               getResources()); - -		return v; -	} - - -	@Override -	public void onDestroy() { -		super.onDestroy(); -		mAltosDroid.unregisterTab(this); -		mAltosDroid = null; -	} - -	public String tab_name() { return "descent"; } - -	public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) { -		if (state != null) { -			set_value(mSpeedView, AltosConvert.speed, 6, state.speed()); -			set_value(mHeightView, AltosConvert.height, 6, state.height()); -			if (from_receiver != null) { -				mElevationView.setText(AltosDroid.number("%3.0f°", from_receiver.elevation)); -				set_value(mRangeView, AltosConvert.distance, 6, from_receiver.range); -				mBearingView.setText(AltosDroid.number("%3.0f°", from_receiver.bearing)); -				mCompassView.setText(from_receiver.bearing_words(AltosGreatCircle.BEARING_LONG)); -				set_value(mDistanceView, AltosConvert.distance, 6, from_receiver.distance); -			} else {  -				mElevationView.setText("<unknown>"); -				mRangeView.setText("<unknown>"); -				mBearingView.setText("<unknown>"); -				mCompassView.setText("<unknown>"); -				mDistanceView.setText("<unknown>"); -			} -			if (state.gps != null) { -				mLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S")); -				mLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W")); -			} - -			mApogeeVoltageView.setText(AltosDroid.number("%4.2f V", state.apogee_voltage)); -			mApogeeLights.set(state.apogee_voltage > 3.2, state.apogee_voltage == AltosLib.MISSING); - -			mMainVoltageView.setText(AltosDroid.number("%4.2f V", state.main_voltage)); -			mMainLights.set(state.main_voltage > 3.2, state.main_voltage == AltosLib.MISSING); -		} -	} - -} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabFlight.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabFlight.java new file mode 100644 index 00000000..a503f1bc --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabFlight.java @@ -0,0 +1,127 @@ +/* + * Copyright © 2013 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 org.altusmetrum.altoslib_8.*; + +import android.app.Activity; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.*; +import android.widget.*; +import android.location.Location; + +public class TabFlight extends AltosDroidTab { +	private TextView speed_view; +	private TextView height_view; +	private TextView max_speed_view; +	private TextView max_height_view; +	private TextView elevation_view; +	private TextView range_view; +	private TextView bearing_view; +	private TextView compass_view; +	private TextView distance_view; +	private TextView latitude_view; +	private TextView longitude_view; +	private View apogee_view; +	private TextView apogee_voltage_view; +	private TextView apogee_voltage_label; +	private GoNoGoLights apogee_lights; +	private View main_view; +	private TextView main_voltage_view; +	private TextView main_voltage_label; +	private GoNoGoLights main_lights; + +	@Override +	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { +		View v = inflater.inflate(R.layout.tab_flight, container, false); + +		speed_view     = (TextView) v.findViewById(R.id.speed_value); +		height_view    = (TextView) v.findViewById(R.id.height_value); +		max_speed_view = (TextView) v.findViewById(R.id.max_speed_value); +		max_height_view= (TextView) v.findViewById(R.id.max_height_value); +		elevation_view = (TextView) v.findViewById(R.id.elevation_value); +		range_view     = (TextView) v.findViewById(R.id.range_value); +		bearing_view   = (TextView) v.findViewById(R.id.bearing_value); +		compass_view   = (TextView) v.findViewById(R.id.compass_value); +		distance_view  = (TextView) v.findViewById(R.id.distance_value); +		latitude_view  = (TextView) v.findViewById(R.id.lat_value); +		longitude_view = (TextView) v.findViewById(R.id.lon_value); + +		apogee_view = v.findViewById(R.id.apogee_view); +		apogee_voltage_view = (TextView) v.findViewById(R.id.apogee_voltage_value); +		apogee_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.apogee_redled), +		                                 (ImageView) v.findViewById(R.id.apogee_greenled), +		                                 getResources()); +		apogee_voltage_label = (TextView) v.findViewById(R.id.apogee_voltage_label); + +		main_view = v.findViewById(R.id.main_view); +		main_voltage_view = (TextView) v.findViewById(R.id.main_voltage_value); +		main_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.main_redled), +		                               (ImageView) v.findViewById(R.id.main_greenled), +		                               getResources()); +		main_voltage_label = (TextView) v.findViewById(R.id.main_voltage_label); + +		return v; +	} + +	public String tab_name() { return AltosDroid.tab_flight_name; } + +	public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) { +		if (state != null) { +			set_value(speed_view, AltosConvert.speed, 6, state.speed()); +			set_value(height_view, AltosConvert.height, 6, state.height()); +			set_value(max_speed_view, AltosConvert.speed, 6, state.max_speed()); +			set_value(max_height_view, AltosConvert.speed, 6, state.max_height()); +			if (from_receiver != null) { +				elevation_view.setText(AltosDroid.number("%3.0f°", from_receiver.elevation)); +				set_value(range_view, AltosConvert.distance, 6, from_receiver.range); +				bearing_view.setText(AltosDroid.number("%3.0f°", from_receiver.bearing)); +				compass_view.setText(from_receiver.bearing_words(AltosGreatCircle.BEARING_LONG)); +				set_value(distance_view, AltosConvert.distance, 6, from_receiver.distance); +			} else {  +				elevation_view.setText("<unknown>"); +				range_view.setText("<unknown>"); +				bearing_view.setText("<unknown>"); +				compass_view.setText("<unknown>"); +				distance_view.setText("<unknown>"); +			} +			if (state.gps != null) { +				latitude_view.setText(AltosDroid.pos(state.gps.lat, "N", "S")); +				longitude_view.setText(AltosDroid.pos(state.gps.lon, "E", "W")); +			} + +			if (state.apogee_voltage == AltosLib.MISSING) { +				apogee_view.setVisibility(View.GONE); +			} else { +				apogee_voltage_view.setText(AltosDroid.number("%4.2f V", state.apogee_voltage)); +				apogee_lights.set(state.apogee_voltage > 3.2, state.apogee_voltage == AltosLib.MISSING); +				apogee_view.setVisibility(View.VISIBLE); +			} + +			if (state.main_voltage == AltosLib.MISSING) { +				main_view.setVisibility(View.GONE); +			} else { +				main_voltage_view.setText(AltosDroid.number("%4.2f V", state.main_voltage)); +				main_lights.set(state.main_voltage > 3.2, state.main_voltage == AltosLib.MISSING); +				main_view.setVisibility(View.VISIBLE); +			} +		} +	} + +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java index 871b94a1..54ccd18f 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java @@ -17,186 +17,159 @@  package org.altusmetrum.AltosDroid; -import java.util.Arrays; +import java.util.*; +import java.io.*; -import org.altusmetrum.altoslib_6.*; - -import com.google.android.gms.maps.CameraUpdateFactory; -import com.google.android.gms.maps.GoogleMap; -import com.google.android.gms.maps.SupportMapFragment; -import com.google.android.gms.maps.model.BitmapDescriptorFactory; -import com.google.android.gms.maps.model.LatLng; -import com.google.android.gms.maps.model.Marker; -import com.google.android.gms.maps.model.MarkerOptions; -import com.google.android.gms.maps.model.Polyline; -import com.google.android.gms.maps.model.PolylineOptions; +import org.altusmetrum.altoslib_8.*;  import android.app.Activity; -import android.graphics.Color; +import android.graphics.*;  import android.os.Bundle;  import android.support.v4.app.Fragment; -//import android.support.v4.app.FragmentTransaction; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; +import android.support.v4.app.FragmentTransaction; +import android.view.*; +import android.widget.*;  import android.location.Location; +import android.content.*;  public class TabMap extends AltosDroidTab { -	AltosDroid mAltosDroid; - -	private SupportMapFragment mMapFragment; -	private GoogleMap mMap; -	private boolean mapLoaded = false; -	private Marker mRocketMarker; -	private Marker mPadMarker; -	private boolean pad_set; -	private Polyline mPolyline; +	AltosLatLon	here;  	private TextView mDistanceView; +	private TextView mBearingLabel;  	private TextView mBearingView;  	private TextView mTargetLatitudeView;  	private TextView mTargetLongitudeView;  	private TextView mReceiverLatitudeView;  	private TextView mReceiverLongitudeView; - -	private double mapAccuracy = -1; +	private AltosMapOffline map_offline; +	private AltosMapOnline map_online; +	private View view; +	private int map_source;  	@Override  	public void onAttach(Activity activity) {  		super.onAttach(activity); -		mAltosDroid = (AltosDroid) activity; -		mAltosDroid.registerTab(this);  	}  	@Override  	public void onCreate(Bundle savedInstanceState) {  		super.onCreate(savedInstanceState); - -		mMapFragment = new SupportMapFragment() { -			@Override -			public void onActivityCreated(Bundle savedInstanceState) { -				super.onActivityCreated(savedInstanceState); -				setupMap(); -			} -		}; -  	}  	@Override  	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { -		View v = inflater.inflate(R.layout.tab_map, container, false); -		mDistanceView  = (TextView)v.findViewById(R.id.distance_value); -		mBearingView   = (TextView)v.findViewById(R.id.bearing_value); -		mTargetLatitudeView  = (TextView)v.findViewById(R.id.target_lat_value); -		mTargetLongitudeView = (TextView)v.findViewById(R.id.target_lon_value); -		mReceiverLatitudeView  = (TextView)v.findViewById(R.id.receiver_lat_value); -		mReceiverLongitudeView = (TextView)v.findViewById(R.id.receiver_lon_value); -		return v; +		view = inflater.inflate(R.layout.tab_map, container, false); +		int map_source = AltosDroidPreferences.map_source(); + +		mDistanceView  = (TextView)view.findViewById(R.id.distance_value); +		mBearingLabel  = (TextView)view.findViewById(R.id.bearing_label); +		mBearingView   = (TextView)view.findViewById(R.id.bearing_value); +		mTargetLatitudeView  = (TextView)view.findViewById(R.id.target_lat_value); +		mTargetLongitudeView = (TextView)view.findViewById(R.id.target_lon_value); +		mReceiverLatitudeView  = (TextView)view.findViewById(R.id.receiver_lat_value); +		mReceiverLongitudeView = (TextView)view.findViewById(R.id.receiver_lon_value); +		map_offline = (AltosMapOffline)view.findViewById(R.id.map_offline); +		map_offline.onCreateView(altos_droid); +		map_online = new AltosMapOnline(view.getContext()); +		map_online.onCreateView(altos_droid); +		set_map_source(AltosDroidPreferences.map_source()); +		return view;  	}  	@Override  	public void onActivityCreated(Bundle savedInstanceState) {  		super.onActivityCreated(savedInstanceState); -		getChildFragmentManager().beginTransaction().add(R.id.map, mMapFragment).commit(); +		if (map_online != null) +			getChildFragmentManager().beginTransaction().add(R.id.map_online, map_online.mMapFragment).commit();  	}  	@Override  	public void onDestroyView() {  		super.onDestroyView(); - -		mAltosDroid.unregisterTab(this); -		mAltosDroid = null; - -		//Fragment fragment = (getFragmentManager().findFragmentById(R.id.map)); -		//FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction(); -		//ft.remove(fragment); -		//ft.commit();  	} -	private void setupMap() { -		mMap = mMapFragment.getMap(); -		if (mMap != null) { -			mMap.setMyLocationEnabled(true); -			mMap.getUiSettings().setTiltGesturesEnabled(false); -			mMap.getUiSettings().setZoomControlsEnabled(false); - -			mRocketMarker = mMap.addMarker( -					// From: http://mapicons.nicolasmollet.com/markers/industry/military/missile-2/ -					new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.drawable.rocket)) -					                   .position(new LatLng(0,0)) -					                   .visible(false) -					); - -			mPadMarker = mMap.addMarker( -					new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.drawable.pad)) -					                   .position(new LatLng(0,0)) -					                   .visible(false) -					); - -			mPolyline = mMap.addPolyline( -					new PolylineOptions().add(new LatLng(0,0), new LatLng(0,0)) -					                     .width(3) -					                     .color(Color.BLUE) -					                     .visible(false) -					); - -			mapLoaded = true; -		} -	} +	public String tab_name() { return AltosDroid.tab_map_name; }  	private void center(double lat, double lon, double accuracy) { -		if (mapAccuracy < 0 || accuracy < mapAccuracy/10) { -			mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(lat, lon),14)); -			mapAccuracy = accuracy; -		} +		if (map_offline != null) +			map_offline.center(lat, lon, accuracy); +		if (map_online != null) +			map_online.center(lat, lon, accuracy);  	} -	public String tab_name() { return "map"; } - -	public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) { +	public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {  		if (from_receiver != null) { -			mBearingView.setText(String.format("%3.0f°", from_receiver.bearing)); +			String	direction = AltosDroid.direction(from_receiver, receiver); +			if (direction != null) { +				mBearingLabel.setText("Direction"); +				mBearingView.setText(direction); +			} else { +				mBearingLabel.setText("Bearing"); +				mBearingView.setText(String.format("%3.0f°", from_receiver.bearing)); +			}  			set_value(mDistanceView, AltosConvert.distance, 6, from_receiver.distance); +		} else { +			mBearingLabel.setText("Bearing"); +			mBearingView.setText(""); +			set_value(mDistanceView, AltosConvert.distance, 6, AltosLib.MISSING);  		}  		if (state != null) { -			if (mapLoaded) { -				if (state.gps != null) { -					mRocketMarker.setPosition(new LatLng(state.gps.lat, state.gps.lon)); -					mRocketMarker.setVisible(true); - -					mPolyline.setPoints(Arrays.asList(new LatLng(state.pad_lat, state.pad_lon), new LatLng(state.gps.lat, state.gps.lon))); -					mPolyline.setVisible(true); -				} - -				if (!pad_set && state.pad_lat != AltosLib.MISSING) { -					pad_set = true; -					mPadMarker.setPosition(new LatLng(state.pad_lat, state.pad_lon)); -					mPadMarker.setVisible(true); -				} -			}  			if (state.gps != null) {  				mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S"));  				mTargetLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W")); -				if (state.gps.locked && state.gps.nsat >= 4) -					center (state.gps.lat, state.gps.lon, 10);  			}  		}  		if (receiver != null) {  			double accuracy; +			here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());  			if (receiver.hasAccuracy())  				accuracy = receiver.getAccuracy();  			else  				accuracy = 1000; -			mReceiverLatitudeView.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S")); -			mReceiverLongitudeView.setText(AltosDroid.pos(receiver.getLongitude(), "E", "W")); -			center (receiver.getLatitude(), receiver.getLongitude(), accuracy); +			mReceiverLatitudeView.setText(AltosDroid.pos(here.lat, "N", "S")); +			mReceiverLongitudeView.setText(AltosDroid.pos(here.lon, "E", "W")); +			center (here.lat, here.lon, accuracy); +		} +		if (map_source == AltosDroidPreferences.MAP_SOURCE_OFFLINE) { +			if (map_offline != null) +				map_offline.show(telem_state, state, from_receiver, receiver); +		} else { +			if (map_online != null) +				map_online.show(telem_state, state, from_receiver, receiver);  		} +	} +	@Override +	public void set_map_type(int map_type) { +		if (map_offline != null) +			map_offline.set_map_type(map_type); +		if (map_online != null) +			map_online.set_map_type(map_type); +	} + +	@Override +	public void set_map_source(int map_source) { +		this.map_source = map_source; +		if (map_source == AltosDroidPreferences.MAP_SOURCE_OFFLINE) { +			if (map_online != null) +				map_online.set_visible(false); +			if (map_offline != null) { +				map_offline.set_visible(true); +				map_offline.show(last_telem_state, last_state, last_from_receiver, last_receiver); +			} +		} else { +			if (map_offline != null) +				map_offline.set_visible(false); +			if (map_online != null) { +				map_online.set_visible(true); +				map_online.show(last_telem_state, last_state, last_from_receiver, last_receiver); +			} +		}  	}  	public TabMap() { diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java index 0ac78219..4d04316f 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java @@ -17,155 +17,222 @@  package org.altusmetrum.AltosDroid; -import org.altusmetrum.altoslib_6.*; +import org.altusmetrum.altoslib_8.*;  import android.app.Activity;  import android.os.Bundle;  import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; +import android.view.*; +import android.widget.*;  import android.location.Location;  public class TabPad extends AltosDroidTab { -	AltosDroid mAltosDroid; - -	private TextView mBatteryVoltageView; -	private TextView mBatteryVoltageLabel; -	private GoNoGoLights mBatteryLights; -	private TextView mApogeeVoltageView; -	private TextView mApogeeVoltageLabel; -	private GoNoGoLights mApogeeLights; -	private TextView mMainVoltageView; -	private TextView mMainVoltageLabel; -	private GoNoGoLights mMainLights; -	private TextView mDataLoggingView; -	private GoNoGoLights mDataLoggingLights; -	private TextView mGPSLockedView; -	private GoNoGoLights mGPSLockedLights; -	private TextView mGPSReadyView; -	private GoNoGoLights mGPSReadyLights; -	private TextView mPadLatitudeView; -	private TextView mPadLongitudeView; -	private TextView mPadAltitudeView; +	private TextView battery_voltage_view; +	private GoNoGoLights battery_lights; + +	private TableRow receiver_row; +	private TextView receiver_voltage_view; +	private TextView receiver_voltage_label; +	private GoNoGoLights receiver_voltage_lights; + +	private TableRow apogee_row; +	private TextView apogee_voltage_view; +	private TextView apogee_voltage_label; +	private GoNoGoLights apogee_lights; + +	private TableRow main_row; +	private TextView main_voltage_view; +	private TextView main_voltage_label; +	private GoNoGoLights main_lights; + +	private TextView data_logging_view; +	private GoNoGoLights data_logging_lights; + +	private TextView gps_locked_view; +	private GoNoGoLights gps_locked_lights; + +	private TextView gps_ready_view; +	private GoNoGoLights gps_ready_lights; + +	private TextView receiver_latitude_view; +	private TextView receiver_longitude_view; +	private TextView receiver_altitude_view; + +	private TableRow[] ignite_row = new TableRow[4]; +	private TextView[] ignite_voltage_view = new TextView[4]; +	private TextView[] ignite_voltage_label = new TextView[4]; +	private GoNoGoLights[] ignite_lights = new GoNoGoLights[4]; -	@Override -	public void onAttach(Activity activity) { -		super.onAttach(activity); -		mAltosDroid = (AltosDroid) activity; -		mAltosDroid.registerTab(this); -	}  	@Override  	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  		View v = inflater.inflate(R.layout.tab_pad, container, false); -		mBatteryVoltageView = (TextView) v.findViewById(R.id.battery_voltage_value); -		mBatteryVoltageLabel = (TextView) v.findViewById(R.id.battery_voltage_label); -		mBatteryLights = new GoNoGoLights((ImageView) v.findViewById(R.id.battery_redled), +		battery_voltage_view = (TextView) v.findViewById(R.id.battery_voltage_value); +		battery_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.battery_redled),  		                                  (ImageView) v.findViewById(R.id.battery_greenled),  		                                  getResources()); -		mApogeeVoltageView = (TextView) v.findViewById(R.id.apogee_voltage_value); -		mApogeeVoltageLabel = (TextView) v.findViewById(R.id.apogee_voltage_label); -		mApogeeLights = new GoNoGoLights((ImageView) v.findViewById(R.id.apogee_redled), +		receiver_row = (TableRow) v.findViewById(R.id.receiver_row); +		receiver_voltage_view = (TextView) v.findViewById(R.id.receiver_voltage_value); +		receiver_voltage_label = (TextView) v.findViewById(R.id.receiver_voltage_label); +		receiver_voltage_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.receiver_redled), +							   (ImageView) v.findViewById(R.id.receiver_greenled), +							   getResources()); + +		apogee_row = (TableRow) v.findViewById(R.id.apogee_row); +		apogee_voltage_view = (TextView) v.findViewById(R.id.apogee_voltage_value); +		apogee_voltage_label = (TextView) v.findViewById(R.id.apogee_voltage_label); +		apogee_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.apogee_redled),  		                                 (ImageView) v.findViewById(R.id.apogee_greenled),  		                                 getResources()); -		mMainVoltageView = (TextView) v.findViewById(R.id.main_voltage_value); -		mMainVoltageLabel = (TextView) v.findViewById(R.id.main_voltage_label); -		mMainLights = new GoNoGoLights((ImageView) v.findViewById(R.id.main_redled), +		main_row = (TableRow) v.findViewById(R.id.main_row); +		main_voltage_view = (TextView) v.findViewById(R.id.main_voltage_value); +		main_voltage_label = (TextView) v.findViewById(R.id.main_voltage_label); +		main_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.main_redled),  		                               (ImageView) v.findViewById(R.id.main_greenled),  		                               getResources()); -		mDataLoggingView = (TextView) v.findViewById(R.id.logging_value); -		mDataLoggingLights = new GoNoGoLights((ImageView) v.findViewById(R.id.logging_redled), +		data_logging_view = (TextView) v.findViewById(R.id.logging_value); +		data_logging_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.logging_redled),  		                                      (ImageView) v.findViewById(R.id.logging_greenled),  		                                      getResources()); -		mGPSLockedView = (TextView) v.findViewById(R.id.gps_locked_value); -		mGPSLockedLights = new GoNoGoLights((ImageView) v.findViewById(R.id.gps_locked_redled), +		gps_locked_view = (TextView) v.findViewById(R.id.gps_locked_value); +		gps_locked_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.gps_locked_redled),  		                                    (ImageView) v.findViewById(R.id.gps_locked_greenled),  		                                    getResources()); -		mGPSReadyView = (TextView) v.findViewById(R.id.gps_ready_value); -		mGPSReadyLights = new GoNoGoLights((ImageView) v.findViewById(R.id.gps_ready_redled), +		gps_ready_view = (TextView) v.findViewById(R.id.gps_ready_value); +		gps_ready_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.gps_ready_redled),  		                                   (ImageView) v.findViewById(R.id.gps_ready_greenled),  		                                   getResources()); -		mPadLatitudeView = (TextView) v.findViewById(R.id.pad_lat_value); -		mPadLongitudeView = (TextView) v.findViewById(R.id.pad_lon_value); -		mPadAltitudeView = (TextView) v.findViewById(R.id.pad_alt_value); -        return v; -	} +		for (int i = 0; i < 4; i++) { +			int row_id, view_id, label_id, lights_id; +			int red_id, green_id; +			switch (i) { +			case 0: +			default: +				row_id = R.id.ignite_a_row; +				view_id = R.id.ignite_a_voltage_value; +				label_id = R.id.ignite_a_voltage_label; +				red_id = R.id.ignite_a_redled; +				green_id = R.id.ignite_a_greenled; +				break; +			case 1: +				row_id = R.id.ignite_b_row; +				view_id = R.id.ignite_b_voltage_value; +				label_id = R.id.ignite_b_voltage_label; +				red_id = R.id.ignite_b_redled; +				green_id = R.id.ignite_b_greenled; +				break; +			case 2: +				row_id = R.id.ignite_c_row; +				view_id = R.id.ignite_c_voltage_value; +				label_id = R.id.ignite_c_voltage_label; +				red_id = R.id.ignite_c_redled; +				green_id = R.id.ignite_c_greenled; +				break; +			case 3: +				row_id = R.id.ignite_d_row; +				view_id = R.id.ignite_d_voltage_value; +				label_id = R.id.ignite_d_voltage_label; +				red_id = R.id.ignite_d_redled; +				green_id = R.id.ignite_d_greenled; +				break; +			} +			ignite_row[i] = (TableRow) v.findViewById(row_id); +			ignite_voltage_view[i] = (TextView) v.findViewById(view_id); +			ignite_voltage_label[i] = (TextView) v.findViewById(label_id); +			ignite_lights[i] = new GoNoGoLights((ImageView) v.findViewById(red_id), +							     (ImageView) v.findViewById(green_id), +							     getResources()); +		} -	@Override -	public void onDestroy() { -		super.onDestroy(); -		mAltosDroid.unregisterTab(this); -		mAltosDroid = null; +		receiver_latitude_view = (TextView) v.findViewById(R.id.receiver_lat_value); +		receiver_longitude_view = (TextView) v.findViewById(R.id.receiver_lon_value); +		receiver_altitude_view = (TextView) v.findViewById(R.id.receiver_alt_value); +        return v;  	} -	public String tab_name() { return "pad"; } +	public String tab_name() { return AltosDroid.tab_pad_name; } -	public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) { +	public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {  		if (state != null) { -			mBatteryVoltageView.setText(AltosDroid.number("%4.2f V", state.battery_voltage)); -			mBatteryLights.set(state.battery_voltage >= AltosLib.ao_battery_good, state.battery_voltage == AltosLib.MISSING); +			battery_voltage_view.setText(AltosDroid.number(" %4.2f V", state.battery_voltage)); +			battery_lights.set(state.battery_voltage >= AltosLib.ao_battery_good, state.battery_voltage == AltosLib.MISSING);  			if (state.apogee_voltage == AltosLib.MISSING) { -				mApogeeVoltageView.setVisibility(View.GONE); -				mApogeeVoltageLabel.setVisibility(View.GONE); +				apogee_row.setVisibility(View.GONE);  			} else { -				mApogeeVoltageView.setText(AltosDroid.number("%4.2f V", state.apogee_voltage)); -				mApogeeVoltageView.setVisibility(View.VISIBLE); -				mApogeeVoltageLabel.setVisibility(View.VISIBLE); +				apogee_voltage_view.setText(AltosDroid.number(" %4.2f V", state.apogee_voltage)); +				apogee_row.setVisibility(View.VISIBLE);  			} -			mApogeeLights.set(state.apogee_voltage >= AltosLib.ao_igniter_good, state.apogee_voltage == AltosLib.MISSING); +			apogee_lights.set(state.apogee_voltage >= AltosLib.ao_igniter_good, state.apogee_voltage == AltosLib.MISSING);  			if (state.main_voltage == AltosLib.MISSING) { -				mMainVoltageView.setVisibility(View.GONE); -				mMainVoltageLabel.setVisibility(View.GONE); +				main_row.setVisibility(View.GONE);  			} else { -				mMainVoltageView.setText(AltosDroid.number("%4.2f V", state.main_voltage)); -				mMainVoltageView.setVisibility(View.VISIBLE); -				mMainVoltageLabel.setVisibility(View.VISIBLE); +				main_voltage_view.setText(AltosDroid.number(" %4.2f V", state.main_voltage)); +				main_row.setVisibility(View.VISIBLE); +			} +			main_lights.set(state.main_voltage >= AltosLib.ao_igniter_good, state.main_voltage == AltosLib.MISSING); + +			int num_igniter = state.ignitor_voltage == null ? 0 : state.ignitor_voltage.length; + +			for (int i = 0; i < 4; i++) { +				double voltage = i >= num_igniter ? AltosLib.MISSING : state.ignitor_voltage[i]; +				if (voltage == AltosLib.MISSING) { +					ignite_row[i].setVisibility(View.GONE); +				} else { +					ignite_voltage_view[i].setText(AltosDroid.number(" %4.2f V", voltage)); +					ignite_row[i].setVisibility(View.VISIBLE); +				} +				ignite_lights[i].set(voltage >= AltosLib.ao_igniter_good, voltage == AltosLib.MISSING);  			} -			mMainLights.set(state.main_voltage >= AltosLib.ao_igniter_good, state.main_voltage == AltosLib.MISSING);  			if (state.flight != 0) {  				if (state.state <= AltosLib.ao_flight_pad) -					mDataLoggingView.setText("Ready to record"); +					data_logging_view.setText("Ready to record");  				else if (state.state < AltosLib.ao_flight_landed) -					mDataLoggingView.setText("Recording data"); +					data_logging_view.setText("Recording data");  				else -					mDataLoggingView.setText("Recorded data"); +					data_logging_view.setText("Recorded data");  			} else { -				mDataLoggingView.setText("Storage full"); +				data_logging_view.setText("Storage full");  			} -			mDataLoggingLights.set(state.flight != 0, state.flight == AltosLib.MISSING); +			data_logging_lights.set(state.flight != 0, state.flight == AltosLib.MISSING);  			if (state.gps != null) {  				int soln = state.gps.nsat;  				int nsat = state.gps.cc_gps_sat != null ? state.gps.cc_gps_sat.length : 0; -				mGPSLockedView.setText(String.format("%4d in soln, %4d in view", soln, nsat)); -				mGPSLockedLights.set(state.gps.locked && state.gps.nsat >= 4, false); +				gps_locked_view.setText(String.format("%d in soln, %d in view", soln, nsat)); +				gps_locked_lights.set(state.gps.locked && state.gps.nsat >= 4, false);  				if (state.gps_ready) -					mGPSReadyView.setText("Ready"); +					gps_ready_view.setText("Ready");  				else -					mGPSReadyView.setText(AltosDroid.integer("Waiting %d", state.gps_waiting)); +					gps_ready_view.setText(AltosDroid.integer("Waiting %d", state.gps_waiting));  			} else -				mGPSLockedLights.set(false, true); -			mGPSReadyLights.set(state.gps_ready, state.gps == null); +				gps_locked_lights.set(false, true); +			gps_ready_lights.set(state.gps_ready, state.gps == null); +		} + +		if (telem_state != null) { +			if (telem_state.receiver_battery == AltosLib.MISSING) { +				receiver_row.setVisibility(View.GONE); +			} else { +				receiver_voltage_view.setText(AltosDroid.number(" %4.2f V", telem_state.receiver_battery)); +				receiver_row.setVisibility(View.VISIBLE); +			} +			receiver_voltage_lights.set(telem_state.receiver_battery >= AltosLib.ao_battery_good, telem_state.receiver_battery == AltosLib.MISSING);  		}  		if (receiver != null) {  			double altitude = AltosLib.MISSING;  			if (receiver.hasAltitude())  				altitude = receiver.getAltitude(); -			mPadLatitudeView.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S")); -			mPadLongitudeView.setText(AltosDroid.pos(receiver.getLongitude(), "E", "W")); -			set_value(mPadAltitudeView, AltosConvert.height, 6, altitude); +			receiver_latitude_view.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S")); +			receiver_longitude_view.setText(AltosDroid.pos(receiver.getLongitude(), "E", "W")); +			set_value(receiver_altitude_view, AltosConvert.height, 1, altitude);  		}  	} -  } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabLanded.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabRecover.java index 4c69d869..19bb79d3 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabLanded.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabRecover.java @@ -17,7 +17,7 @@  package org.altusmetrum.AltosDroid; -import org.altusmetrum.altoslib_6.*; +import org.altusmetrum.altoslib_8.*;  import android.app.Activity;  import android.os.Bundle; @@ -28,10 +28,9 @@ import android.view.ViewGroup;  import android.widget.TextView;  import android.location.Location; -public class TabLanded extends AltosDroidTab { -	AltosDroid mAltosDroid; - +public class TabRecover extends AltosDroidTab {  	private TextView mBearingView; +	private TextView mDirectionView;  	private TextView mDistanceView;  	private TextView mTargetLatitudeView;  	private TextView mTargetLongitudeView; @@ -41,19 +40,12 @@ public class TabLanded extends AltosDroidTab {  	private TextView mMaxSpeedView;  	private TextView mMaxAccelView; - -	@Override -	public void onAttach(Activity activity) { -		super.onAttach(activity); -		mAltosDroid = (AltosDroid) activity; -		mAltosDroid.registerTab(this); -	} -  	@Override  	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { -		View v = inflater.inflate(R.layout.tab_landed, container, false); +		View v = inflater.inflate(R.layout.tab_recover, container, false);  		mBearingView   = (TextView) v.findViewById(R.id.bearing_value); +		mDirectionView = (TextView) v.findViewById(R.id.direction_value);  		mDistanceView  = (TextView) v.findViewById(R.id.distance_value);  		mTargetLatitudeView  = (TextView) v.findViewById(R.id.target_lat_value);  		mTargetLongitudeView = (TextView) v.findViewById(R.id.target_lon_value); @@ -66,19 +58,17 @@ public class TabLanded extends AltosDroidTab {  		return v;  	} -	@Override -	public void onDestroy() { -		super.onDestroy(); -		mAltosDroid.unregisterTab(this); -		mAltosDroid = null; -	} - -	public String tab_name() { return "landed"; } +	public String tab_name() { return AltosDroid.tab_recover_name; } -	public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) { +	public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {  		if (from_receiver != null) {  			mBearingView.setText(String.format("%3.0f°", from_receiver.bearing));  			set_value(mDistanceView, AltosConvert.distance, 6, from_receiver.distance); +			String direction = AltosDroid.direction(from_receiver, receiver); +			if (direction == null) +				mDirectionView.setText(""); +			else +				mDirectionView.setText(direction);  		}  		if (state != null && state.gps != null) {  			mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S")); diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabsAdapter.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabsAdapter.java index 1ac34f9d..b34a25b6 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabsAdapter.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabsAdapter.java @@ -29,7 +29,6 @@ import android.view.View;  import android.view.ViewGroup;  import android.widget.TabHost;  import android.widget.TabWidget; -import android.util.Log;  /**   * This is a helper class that implements the management of tabs and all @@ -106,7 +105,7 @@ public class TabsAdapter extends FragmentPagerAdapter  	@Override  	public Fragment getItem(int position) {  		TabInfo info = mTabs.get(position); -		Log.d(AltosDroid.TAG, String.format("TabsAdapter.getItem(%d)", position)); +		AltosDebug.debug("TabsAdapter.getItem(%d)", position);  		info.fragment = Fragment.instantiate(mContext, info.clss.getName(), info.args);  		return info.fragment;  	} @@ -131,7 +130,7 @@ public class TabsAdapter extends FragmentPagerAdapter  		if (cur_frag != null) {  			cur_frag.set_visible(true);  		} -		Log.d(AltosDroid.TAG, String.format("TabsAdapter.onTabChanged(%s) = %d", tabId, position)); +		AltosDebug.debug("TabsAdapter.onTabChanged(%s) = %d", tabId, position);  		mViewPager.setCurrentItem(position);  	} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryLogger.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryLogger.java index 9764ab72..79020c16 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryLogger.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryLogger.java @@ -1,18 +1,14 @@  package org.altusmetrum.AltosDroid; -import org.altusmetrum.altoslib_6.*; +import org.altusmetrum.altoslib_8.*;  import android.content.BroadcastReceiver;  import android.content.Context;  import android.content.Intent;  import android.content.IntentFilter;  import android.os.Environment; -import android.util.Log;  public class TelemetryLogger { -	private static final String TAG = "TelemetryLogger"; -	private static final boolean D = true; -  	private Context   context = null;  	private AltosLink link    = null;  	private AltosLog  logger  = null; @@ -33,21 +29,21 @@ public class TelemetryLogger {  	private void close() {  		if (logger != null) { -			if (D) Log.d(TAG, "Shutting down Telemetry Logging"); +			AltosDebug.debug("Shutting down Telemetry Logging");  			logger.close();  			logger = null;  		}  	} -	 +  	void handleExternalStorageState() {  		String state = Environment.getExternalStorageState();  		if (Environment.MEDIA_MOUNTED.equals(state)) {  			if (logger == null) { -				if (D) Log.d(TAG, "Starting up Telemetry Logging"); +				AltosDebug.debug("Starting up Telemetry Logging");  				logger = new AltosLog(link);  			}  		} else { -			if (D) Log.d(TAG, "External Storage not present - stopping"); +			AltosDebug.debug("External Storage not present - stopping");  			close();  		}  	} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java index 4e4408d5..3199f252 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java @@ -21,42 +21,32 @@ package org.altusmetrum.AltosDroid;  import java.text.*;  import java.io.*; +import java.util.*;  import java.util.concurrent.*; -import android.util.Log;  import android.os.Handler; -import org.altusmetrum.altoslib_6.*; +import org.altusmetrum.altoslib_8.*;  public class TelemetryReader extends Thread { -	private static final String TAG = "TelemetryReader"; -	private static final boolean D = true; -  	int         crc_errors;  	Handler     handler;  	AltosLink   link; -	AltosState  state = null;  	LinkedBlockingQueue<AltosLine> telemQueue; -	public AltosState read() throws ParseException, AltosCRCException, InterruptedException, IOException { +	public AltosTelemetry read() throws ParseException, AltosCRCException, InterruptedException, IOException {  		AltosLine l = telemQueue.take();  		if (l.line == null)  			throw new IOException("IO error");  		AltosTelemetry telem = AltosTelemetryLegacy.parse(l.line); -		if (state == null) -			state = new AltosState(); -		else -			state = state.clone(); -		telem.update_state(state); -		return state; +		return telem;  	}  	public void close() { -		state = null;  		link.remove_monitor(telemQueue);  		link = null;  		telemQueue.clear(); @@ -64,16 +54,14 @@ public class TelemetryReader extends Thread {  	}  	public void run() { -		AltosState  state = null; -  		try { -			if (D) Log.d(TAG, "starting loop"); +			AltosDebug.debug("starting loop");  			while (telemQueue != null) {  				try { -					state = read(); -					handler.obtainMessage(TelemetryService.MSG_TELEMETRY, state).sendToTarget(); +					AltosTelemetry	telem = read(); +					handler.obtainMessage(TelemetryService.MSG_TELEMETRY, telem).sendToTarget();  				} catch (ParseException pp) { -					Log.e(TAG, String.format("Parse error: %d \"%s\"", pp.getErrorOffset(), pp.getMessage())); +					AltosDebug.error("Parse error: %d \"%s\"", pp.getErrorOffset(), pp.getMessage());  				} catch (AltosCRCException ce) {  					++crc_errors;  					handler.obtainMessage(TelemetryService.MSG_CRC_ERROR, new Integer(crc_errors)).sendToTarget(); @@ -81,21 +69,22 @@ public class TelemetryReader extends Thread {  			}  		} catch (InterruptedException ee) {  		} catch (IOException ie) { +			AltosDebug.error("IO exception in telemetry reader"); +			handler.obtainMessage(TelemetryService.MSG_DISCONNECTED, link).sendToTarget();  		} finally {  			close();  		}  	} -	public TelemetryReader (AltosLink in_link, Handler in_handler, AltosState in_state) { -		if (D) Log.d(TAG, "connected TelemetryReader create started"); +	public TelemetryReader (AltosLink in_link, Handler in_handler) { +		AltosDebug.debug("connected TelemetryReader create started");  		link    = in_link;  		handler = in_handler; -		state = in_state;  		telemQueue = new LinkedBlockingQueue<AltosLine>();  		link.add_monitor(telemQueue);  		link.set_telemetry(AltosLib.ao_telemetry_standard); -		if (D) Log.d(TAG, "connected TelemetryReader created"); +		AltosDebug.debug("connected TelemetryReader created");  	}  } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java index d4ac66aa..4a056d95 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java @@ -18,10 +18,8 @@  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 java.util.*;  import android.app.Notification;  //import android.app.NotificationManager; @@ -29,6 +27,7 @@ import android.app.PendingIntent;  import android.app.Service;  import android.bluetooth.BluetoothDevice;  import android.bluetooth.BluetoothAdapter; +import android.hardware.usb.*;  import android.content.Intent;  import android.content.Context;  import android.os.Bundle; @@ -38,55 +37,50 @@ import android.os.Message;  import android.os.Messenger;  import android.os.RemoteException;  import android.os.Looper; -import android.util.Log;  import android.widget.Toast;  import android.location.Location;  import android.location.LocationManager;  import android.location.LocationListener;  import android.location.Criteria; -import org.altusmetrum.altoslib_6.*; - +import org.altusmetrum.altoslib_8.*;  public class TelemetryService extends Service implements LocationListener { -	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; -	static final int MSG_CRC_ERROR	       = 9; -	static final int MSG_SETBAUD	       = 10; +	static final int MSG_OPEN_USB	       = 4; +	static final int MSG_CONNECTED         = 5; +	static final int MSG_CONNECT_FAILED    = 6; +	static final int MSG_DISCONNECTED      = 7; +	static final int MSG_TELEMETRY         = 8; +	static final int MSG_SETFREQUENCY      = 9; +	static final int MSG_CRC_ERROR	       = 10; +	static final int MSG_SETBAUD	       = 11; +	static final int MSG_DISCONNECT	       = 12; +	static final int MSG_DELETE_SERIAL     = 13;  	// 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. +	ArrayList<Messenger> clients = new ArrayList<Messenger>(); // Keeps track of all current registered clients. +	final Handler   handler   = new IncomingHandler(this); +	final Messenger messenger = new Messenger(handler); // Target we publish for clients to send messages to IncomingHandler.  	// Name of the connected device -	String address; -	private AltosBluetooth  mAltosBluetooth  = null; -	private TelemetryReader mTelemetryReader = null; -	private TelemetryLogger mTelemetryLogger = null; -	// Local Bluetooth adapter -	private BluetoothAdapter mBluetoothAdapter = null; +	DeviceAddress address; +	private AltosDroidLink  altos_link  = null; +	private TelemetryReader telemetry_reader = null; +	private TelemetryLogger telemetry_logger = null; -	private TelemetryState	telemetry_state; +	// Local Bluetooth adapter +	private BluetoothAdapter bluetooth_adapter = null;  	// Last data seen; send to UI when it starts +	private TelemetryState	telemetry_state;  	// Handler of incoming messages from clients.  	static class IncomingHandler extends Handler { @@ -96,83 +90,121 @@ public class TelemetryService extends Service implements LocationListener {  		@Override  		public void handleMessage(Message msg) {  			TelemetryService s = service.get(); +			AltosDroidLink bt = null;  			if (s == null)  				return;  			switch (msg.what) { + +				/* Messages from application */  			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 -					msg.replyTo.send(s.message()); -				} catch (RemoteException e) { -					s.mClients.remove(msg.replyTo); -				} -				if (D) Log.d(TAG, "Client bound to service"); +				s.add_client(msg.replyTo);  				break;  			case MSG_UNREGISTER_CLIENT: -				s.mClients.remove(msg.replyTo); -				if (D) Log.d(TAG, "Client unbound from service"); +				s.remove_client(msg.replyTo);  				break;  			case MSG_CONNECT: -				if (D) Log.d(TAG, "Connect command received"); -				String address = (String) msg.obj; +				AltosDebug.debug("Connect command received"); +				DeviceAddress address = (DeviceAddress) msg.obj;  				AltosDroidPreferences.set_active_device(address); -				s.startAltosBluetooth(address); -				break; -			case MSG_CONNECTED: -				if (D) Log.d(TAG, "Connected to device"); -				try { -					s.connected(); -				} catch (InterruptedException ie) { -				} +				s.start_altos_bluetooth(address, false);  				break; -			case MSG_CONNECT_FAILED: -				if (D) Log.d(TAG, "Connection failed... retrying"); -				if (s.address != null) -					s.startAltosBluetooth(s.address); +			case MSG_OPEN_USB: +				AltosDebug.debug("Open USB command received"); +				UsbDevice device = (UsbDevice) msg.obj; +				s.start_usb(device);  				break; -			case MSG_DISCONNECTED: -				Log.d(TAG, "MSG_DISCONNECTED"); -				s.stopAltosBluetooth(); +			case MSG_DISCONNECT: +				AltosDebug.debug("Disconnect command received"); +				s.address = null; +				s.disconnect(true);  				break; -			case MSG_TELEMETRY: -				// forward telemetry messages -				s.telemetry_state.state = (AltosState) msg.obj; -				if (s.telemetry_state.state != null) { -					if (D) Log.d(TAG, "Save state"); -					AltosPreferences.set_state(0, s.telemetry_state.state, null); -				} -				if (D) Log.d(TAG, "MSG_TELEMETRY"); -				s.sendMessageToClients(); -				break; -			case MSG_CRC_ERROR: -				// forward crc error messages -				s.telemetry_state.crc_errors = (Integer) msg.obj; -				if (D) Log.d(TAG, "MSG_CRC_ERROR"); -				s.sendMessageToClients(); +			case MSG_DELETE_SERIAL: +				AltosDebug.debug("Delete Serial command received"); +				s.delete_serial((Integer) msg.obj);  				break;  			case MSG_SETFREQUENCY: -				if (D) Log.d(TAG, "MSG_SETFREQUENCY"); +				AltosDebug.debug("MSG_SETFREQUENCY");  				s.telemetry_state.frequency = (Double) msg.obj;  				if (s.telemetry_state.connect == TelemetryState.CONNECT_CONNECTED) {  					try { -						s.mAltosBluetooth.set_radio_frequency(s.telemetry_state.frequency); -						s.mAltosBluetooth.save_frequency(); +						s.altos_link.set_radio_frequency(s.telemetry_state.frequency); +						s.altos_link.save_frequency();  					} catch (InterruptedException e) {  					} catch (TimeoutException e) {  					}  				} -				s.sendMessageToClients(); +				s.send_to_clients();  				break;  			case MSG_SETBAUD: -				if (D) Log.d(TAG, "MSG_SETBAUD"); +				AltosDebug.debug("MSG_SETBAUD");  				s.telemetry_state.telemetry_rate = (Integer) msg.obj;  				if (s.telemetry_state.connect == TelemetryState.CONNECT_CONNECTED) { -					s.mAltosBluetooth.set_telemetry_rate(s.telemetry_state.telemetry_rate); -					s.mAltosBluetooth.save_telemetry_rate(); +					s.altos_link.set_telemetry_rate(s.telemetry_state.telemetry_rate); +					s.altos_link.save_telemetry_rate(); +				} +				s.send_to_clients(); +				break; + +				/* +				 *Messages from AltosBluetooth +				 */ +			case MSG_CONNECTED: +				AltosDebug.debug("MSG_CONNECTED"); +				bt = (AltosDroidLink) msg.obj; + +				if (bt != s.altos_link) { +					AltosDebug.debug("Stale message"); +					break; +				} +				AltosDebug.debug("Connected to device"); +				try { +					s.connected(); +				} catch (InterruptedException ie) { +				} +				break; +			case MSG_CONNECT_FAILED: +				AltosDebug.debug("MSG_CONNECT_FAILED"); +				bt = (AltosDroidLink) msg.obj; + +				if (bt != s.altos_link) { +					AltosDebug.debug("Stale message"); +					break; +				} +				if (s.address != null) { +					AltosDebug.debug("Connection failed... retrying"); +					s.start_altos_bluetooth(s.address, true); +				} else { +					s.disconnect(true);  				} -				s.sendMessageToClients(); +				break; +			case MSG_DISCONNECTED: + +				/* This can be sent by either AltosDroidLink or TelemetryReader */ +				AltosDebug.debug("MSG_DISCONNECTED"); +				bt = (AltosDroidLink) msg.obj; + +				if (bt != s.altos_link) { +					AltosDebug.debug("Stale message"); +					break; +				} +				if (s.address != null) { +					AltosDebug.debug("Connection lost... retrying"); +					s.start_altos_bluetooth(s.address, true); +				} else { +					s.disconnect(true); +				} +				break; + +				/* +				 * Messages from TelemetryReader +				 */ +			case MSG_TELEMETRY: +				s.telemetry((AltosTelemetry) msg.obj); +				break; +			case MSG_CRC_ERROR: +				// forward crc error messages +				s.telemetry_state.crc_errors = (Integer) msg.obj; +				s.send_to_clients();  				break;  			default:  				super.handleMessage(msg); @@ -180,161 +212,288 @@ public class TelemetryService extends Service implements LocationListener {  		}  	} +	/* Handle telemetry packet +	 */ +	private void telemetry(AltosTelemetry telem) { +		AltosState	state; + +		if (telemetry_state.states.containsKey(telem.serial)) +			state = telemetry_state.states.get(telem.serial).clone(); +		else +			state = new AltosState(); +		telem.update_state(state); +		telemetry_state.states.put(telem.serial, state); +		if (state != null) { +			AltosPreferences.set_state(telem.serial, state, null); +		} +		send_to_clients(); +	} + +	/* Construct the message to deliver to clients +	 */  	private Message message() {  		if (telemetry_state == null) -			Log.d(TAG, "telemetry_state null!"); -		if (telemetry_state.state == null) -			Log.d(TAG, "telemetry_state.state null!"); +			AltosDebug.debug("telemetry_state null!"); +		if (telemetry_state.states == null) +			AltosDebug.debug("telemetry_state.states null!");  		return Message.obtain(null, AltosDroid.MSG_STATE, telemetry_state);  	} -	private void sendMessageToClients() { -		Message m = message(); -		if (D) Log.d(TAG, String.format("Send message to %d clients", mClients.size())); -		for (int i=mClients.size()-1; i>=0; i--) { -			try { -				if (D) Log.d(TAG, String.format("Send message to client %d", i)); -				mClients.get(i).send(m); -			} catch (RemoteException e) { -				mClients.remove(i); -			} +	/* A new friend has connected +	 */ +	private void add_client(Messenger client) { + +		clients.add(client); +		AltosDebug.debug("Client bound to service"); + +		/* On connect, send the current state to the new client +		 */ +		send_to_client(client, message()); + +		/* If we've got an address from a previous session, then +		 * go ahead and try to reconnect to the device +		 */ +		if (address != null && telemetry_state.connect == TelemetryState.CONNECT_DISCONNECTED) { +			AltosDebug.debug("Reconnecting now..."); +			start_altos_bluetooth(address, false);  		}  	} -	private void stopAltosBluetooth() { -		if (D) Log.d(TAG, "stopAltosBluetooth(): begin"); -		telemetry_state.connect = TelemetryState.CONNECT_READY; -		if (mTelemetryReader != null) { -			if (D) Log.d(TAG, "stopAltosBluetooth(): stopping TelemetryReader"); -			mTelemetryReader.interrupt(); +	/* A client has disconnected, clean up +	 */ +	private void remove_client(Messenger client) { +		clients.remove(client); +		AltosDebug.debug("Client unbound from service"); + +		/* When the list of clients is empty, stop the service if +		 * we have no current telemetry source +		 */ + +		 if (clients.isEmpty() && telemetry_state.connect == TelemetryState.CONNECT_DISCONNECTED) { +			 AltosDebug.debug("No clients, no connection. Stopping\n"); +			 stopSelf(); +		 } +	} + +	private void send_to_client(Messenger client, Message m) { +		try { +			client.send(m); +		} catch (RemoteException e) { +			AltosDebug.error("Client %s disappeared", client.toString()); +			remove_client(client); +		} +	} + +	private void send_to_clients() { +		Message m = message(); +		for (Messenger client : clients) +			send_to_client(client, m); +	} + +	private void disconnect(boolean notify) { +		AltosDebug.debug("disconnect(): begin"); + +		telemetry_state.connect = TelemetryState.CONNECT_DISCONNECTED; +		telemetry_state.address = null; + +		if (altos_link != null) +			altos_link.closing(); + +		stop_receiver_voltage_timer(); + +		if (telemetry_reader != null) { +			AltosDebug.debug("disconnect(): stopping TelemetryReader"); +			telemetry_reader.interrupt();  			try { -				mTelemetryReader.join(); +				telemetry_reader.join();  			} catch (InterruptedException e) {  			} -			mTelemetryReader = null; +			telemetry_reader = null;  		} -		if (mTelemetryLogger != null) { -			if (D) Log.d(TAG, "stopAltosBluetooth(): stopping TelemetryLogger"); -			mTelemetryLogger.stop(); -			mTelemetryLogger = null; +		if (telemetry_logger != null) { +			AltosDebug.debug("disconnect(): stopping TelemetryLogger"); +			telemetry_logger.stop(); +			telemetry_logger = null;  		} -		if (mAltosBluetooth != null) { -			if (D) Log.d(TAG, "stopAltosBluetooth(): stopping AltosBluetooth"); -			mAltosBluetooth.close(); -			mAltosBluetooth = null; +		if (altos_link != null) { +			AltosDebug.debug("disconnect(): stopping AltosDroidLink"); +			altos_link.close(); +			altos_link = null;  		}  		telemetry_state.config = null; -		if (D) Log.d(TAG, "stopAltosBluetooth(): send message to clients"); -		sendMessageToClients(); +		if (notify) { +			AltosDebug.debug("disconnect(): send message to clients"); +			send_to_clients(); +			if (clients.isEmpty()) { +				AltosDebug.debug("disconnect(): no clients, terminating"); +				stopSelf(); +			} +		}  	} -	private void startAltosBluetooth(String address) { +	private void start_usb(UsbDevice device) { +		AltosUsb	d = new AltosUsb(this, device, handler); + +		if (d != null) { +			disconnect(false); +			altos_link = d; +			try { +				connected(); +			} catch (InterruptedException ie) { +			} +		} +	} + +	private void delete_serial(int serial) { +		telemetry_state.states.remove((Integer) serial); +		AltosPreferences.remove_state(serial); +		send_to_clients(); +	} + +	private void start_altos_bluetooth(DeviceAddress address, boolean pause) {  		// Get the BLuetoothDevice object -		BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); +		BluetoothDevice device = bluetooth_adapter.getRemoteDevice(address.address); +		disconnect(false);  		this.address = address; -		if (mAltosBluetooth == null) { -			if (D) Log.d(TAG, String.format("startAltosBluetooth(): Connecting to %s (%s)", device.getName(), device.getAddress())); -			mAltosBluetooth = new AltosBluetooth(device, mHandler); -			telemetry_state.connect = TelemetryState.CONNECT_CONNECTING; -			sendMessageToClients(); -		} 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, address), 3000); -			stopAltosBluetooth(); +		AltosDebug.debug("start_altos_bluetooth(): Connecting to %s (%s)", device.getName(), device.getAddress()); +		altos_link = new AltosBluetooth(device, handler, pause); +		telemetry_state.connect = TelemetryState.CONNECT_CONNECTING; +		telemetry_state.address = address; +		send_to_clients(); +	} + +	// Timer for receiver battery voltage monitoring +	Timer receiver_voltage_timer; + +	private void update_receiver_voltage() { +		if (altos_link != null) { +			try { +				double	voltage = altos_link.monitor_battery(); +				telemetry_state.receiver_battery = voltage; +			} catch (InterruptedException ie) { +			} +		} +	} + +	private void stop_receiver_voltage_timer() { +		if (receiver_voltage_timer != null) { +			receiver_voltage_timer.cancel(); +			receiver_voltage_timer.purge(); +			receiver_voltage_timer = null; +		} +	} + +	private void start_receiver_voltage_timer() { +		if (receiver_voltage_timer == null && altos_link.has_monitor_battery()) { +			receiver_voltage_timer = new Timer(); +			receiver_voltage_timer.scheduleAtFixedRate(new TimerTask() { public void run() {update_receiver_voltage();}}, 1000L, 10000L);  		}  	}  	private void connected() throws InterruptedException { -		if (D) Log.d(TAG, "connected top"); +		AltosDebug.debug("connected top"); +		AltosDebug.check_ui("connected\n");  		try { -			if (mAltosBluetooth == null) +			if (altos_link == null)  				throw new InterruptedException("no bluetooth"); -			telemetry_state.config = mAltosBluetooth.config_data(); -			mAltosBluetooth.set_radio_frequency(telemetry_state.frequency); -			mAltosBluetooth.set_telemetry_rate(telemetry_state.telemetry_rate); +			telemetry_state.config = altos_link.config_data(); +			altos_link.set_radio_frequency(telemetry_state.frequency); +			altos_link.set_telemetry_rate(telemetry_state.telemetry_rate);  		} 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(); +			AltosDebug.debug("connected timeout"); +			if (address != null) { +				AltosDebug.debug("connected timeout, retrying"); +				start_altos_bluetooth(address, true); +			} else { +				handler.obtainMessage(MSG_CONNECT_FAILED).sendToTarget(); +				disconnect(true); +			}  			return;  		} -		if (D) Log.d(TAG, "connected bluetooth configured"); +		AltosDebug.debug("connected bluetooth configured");  		telemetry_state.connect = TelemetryState.CONNECT_CONNECTED; +		telemetry_state.address = address; -		mTelemetryReader = new TelemetryReader(mAltosBluetooth, mHandler, telemetry_state.state); -		mTelemetryReader.start(); +		telemetry_reader = new TelemetryReader(altos_link, handler); +		telemetry_reader.start(); -		if (D) Log.d(TAG, "connected TelemetryReader started"); +		AltosDebug.debug("connected TelemetryReader started"); -		mTelemetryLogger = new TelemetryLogger(this, mAltosBluetooth); +		telemetry_logger = new TelemetryLogger(this, altos_link); -		if (D) Log.d(TAG, "Notify UI of connection"); +		start_receiver_voltage_timer(); -		sendMessageToClients(); -	} +		AltosDebug.debug("Notify UI of connection"); -	private void onTimerTick() { -		if (D) Log.d(TAG, "Timer wakeup"); -		try { -			if (mClients.size() <= 0 && telemetry_state.connect != TelemetryState.CONNECT_CONNECTED) { -				stopSelf(); -			} -		} catch (Throwable t) { -			Log.e(TAG, "Timer failed: ", t); -		} +		send_to_clients();  	}  	@Override  	public void onCreate() { + +		AltosDebug.init(this); + +		// Initialise preferences +		AltosDroidPreferences.init(this); +  		// Get local Bluetooth adapter -		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); +		bluetooth_adapter = BluetoothAdapter.getDefaultAdapter();  		// If the adapter is null, then Bluetooth is not supported -		if (mBluetoothAdapter == null) { +		if (bluetooth_adapter == null) {  			Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();  		} -		// Initialise preferences -		AltosDroidPreferences.init(this); -  		telemetry_state = new TelemetryState();  		// Create a reference to the NotificationManager so that we can update our notifcation text later  		//mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); -		telemetry_state.connect = TelemetryState.CONNECT_READY; +		telemetry_state.connect = TelemetryState.CONNECT_DISCONNECTED; +		telemetry_state.address = null; -		AltosSavedState saved_state = AltosPreferences.state(0); +		/* Pull the saved state information out of the preferences database +		 */ +		ArrayList<Integer> serials = AltosPreferences.list_states(); -		if (saved_state != null) { -			if (D) Log.d(TAG, String.format("recovered old state flight %d\n", saved_state.state.flight)); -			telemetry_state.state = saved_state.state; -		} +		telemetry_state.latest_serial = AltosPreferences.latest_state(); -		// Start our timer - first event in 10 seconds, then every 10 seconds after that. -		timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 10000L, 10000L); +		for (int serial : serials) { +			AltosSavedState saved_state = AltosPreferences.state(serial); +			if (saved_state != null) { +				if (serial == 0) { +					serial = saved_state.state.serial; +					AltosPreferences.set_state(serial, saved_state.state, saved_state.listener_state); +					AltosPreferences.remove_state(0); +				} +				if (telemetry_state.latest_serial == 0) +					telemetry_state.latest_serial = serial; + +				AltosDebug.debug("recovered old state serial %d flight %d\n", +						 serial, +						 saved_state.state.flight); +				if (saved_state.state.gps != null) +					AltosDebug.debug("\tposition %f,%f\n", +							 saved_state.state.gps.lat, +							 saved_state.state.gps.lon); +				telemetry_state.states.put(serial, saved_state.state); +			} +		}  		// Listen for GPS and Network position updates  		LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);  		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this); - -		String address = AltosDroidPreferences.active_device(); -		if (address != null) -			startAltosBluetooth(address);  	}  	@Override  	public int onStartCommand(Intent intent, int flags, int startId) { -		Log.i("TelemetryService", "Received start id " + startId + ": " + intent); +		AltosDebug.debug("Received start id %d: %s", startId, intent);  		CharSequence text = getText(R.string.telemetry_service_started); @@ -354,6 +513,20 @@ public class TelemetryService extends Service implements LocationListener {  		// Move us into the foreground.  		startForeground(NOTIFICATION, notification); +		/* Start bluetooth if we don't have a connection already */ +		if (intent != null && +		    (telemetry_state.connect == TelemetryState.CONNECT_NONE || +		     telemetry_state.connect == TelemetryState.CONNECT_DISCONNECTED)) +		{ +			String	action = intent.getAction(); + +			if (action.equals(AltosDroid.ACTION_BLUETOOTH)) { +				DeviceAddress address = AltosDroidPreferences.active_device(); +				if (address != null && !address.address.startsWith("USB")) +					start_altos_bluetooth(address, false); +			} +		} +  		// We want this service to continue running until it is explicitly  		// stopped, so return sticky.  		return START_STICKY; @@ -366,28 +539,25 @@ public class TelemetryService extends Service implements LocationListener {  		((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this);  		// Stop the bluetooth Comms threads -		stopAltosBluetooth(); +		disconnect(true);  		// 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(); +		return messenger.getBinder();  	}  	public void onLocationChanged(Location location) {  		telemetry_state.location = location; -		if (D) Log.d(TAG, "location changed"); -		sendMessageToClients(); +		AltosDebug.debug("location changed"); +		send_to_clients();  	}  	public void onStatusChanged(String provider, int status, Bundle extras) { diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryState.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryState.java index 862847d2..f9191a32 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryState.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryState.java @@ -17,29 +17,36 @@  package org.altusmetrum.AltosDroid; -import org.altusmetrum.altoslib_6.*; +import java.util.*; +import org.altusmetrum.altoslib_8.*;  import android.location.Location;  public class TelemetryState { -	public static final int CONNECT_NONE       = 0; -	public static final int CONNECT_READY      = 1; -	public static final int CONNECT_CONNECTING = 2; -	public static final int CONNECT_CONNECTED  = 3; +	public static final int CONNECT_NONE         = 0; +	public static final int CONNECT_DISCONNECTED = 1; +	public static final int CONNECT_CONNECTING   = 2; +	public static final int CONNECT_CONNECTED    = 3;  	int		connect; +	DeviceAddress	address;  	AltosConfigData	config; -	AltosState	state;  	Location	location;  	int		crc_errors; +	double		receiver_battery;  	double		frequency;  	int		telemetry_rate; +	HashMap<Integer,AltosState>	states; + +	int		latest_serial; +  	public TelemetryState() {  		connect = CONNECT_NONE;  		config = null; -		state = null; +		states = new HashMap<Integer,AltosState>();  		location = null;  		crc_errors = 0; +		receiver_battery = AltosLib.MISSING;  		frequency = AltosPreferences.frequency(0);  		telemetry_rate = AltosPreferences.telemetry_rate(0);  	}  | 
