Initial Commit - Copy from Altus Metrum AltOS

This commit is contained in:
Jannis Bergmann 2024-06-25 19:03:04 +02:00
commit 13fc49c923
2048 changed files with 1206748 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
ao-tools/libaltos/altos.dll export-ignore
ao-tools/libaltos/libaltos.dylib export-ignore

68
.gitignore vendored Normal file
View File

@ -0,0 +1,68 @@
*~
*.a
*.adb
*.asm
*.cdb
*.ihx
*.lnk
*.lst
*.map
*.mdwn
*.mem
*.o
*.rel
*.rst
*.sym
.deps
TAGS
aclocal.m4
libtool
ltmain.sh
src/Version
src/ao_flight_test
src/ao_gps_test
src/ao_gps_test_skytraq
src/ao_kalman.h
ao-teledongle.h
ao-telemetrum.h
ao-teleterra.h
ao-tidongle.h
ao-tools/ao-bitbang/ao-bitbang
ao-tools/ao-dbg/ao-dbg
ao-tools/ao-dumplog/ao-dumplog
ao-tools/ao-dump-up/ao-dump-up
ao-tools/ao-eeprom/ao-eeprom
ao-tools/ao-edit-telem/ao-edit-telem
ao-tools/ao-elftohex/ao-elftohex
ao-tools/ao-list/ao-list
ao-tools/ao-load/ao-load
ao-tools/ao-postflight/ao-postflight
ao-tools/ao-rawload/ao-rawload
ao-tools/ao-send-telem/ao-send-telem
ao-tools/ao-sky-flash/ao-sky-flash
ao-tools/ao-usbload/ao-usbload
ao-tools/ao-view/ao-view
ao-view/Makefile
ao-view/ao-view
autom4te.cache
compile
config.*
config.h
config.h.in
config.log
config.status
build-stamp
configure-stamp
configure
depcomp
install-sh
Makefile
Makefile.in
missing
stamp-h1
tags
doc/telemetrum.fo
doc/telemetrum.html
doc/telemetrum.pdf
altosui/altos-windows.log
pdclib-root

2
AUTHORS Normal file
View File

@ -0,0 +1,2 @@
Keith Packard <keithp@keithp.com>
Bdale Garbee <bdale@gag.com>

339
COPYING Normal file
View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

65570
ChangeLog Normal file

File diff suppressed because it is too large Load Diff

365
INSTALL Normal file
View File

@ -0,0 +1,365 @@
Installation Instructions
*************************
Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,
2006, 2007, 2008, 2009 Free Software Foundation, Inc.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. This file is offered as-is,
without warranty of any kind.
Basic Installation
==================
Briefly, the shell commands `./configure; make; make install' should
configure, build, and install this package. The following
more-detailed instructions are generic; see the `README' file for
instructions specific to this package. Some packages provide this
`INSTALL' file but do not implement all of the features documented
below. The lack of an optional feature in a given package is not
necessarily a bug. More recommendations for GNU packages can be found
in *note Makefile Conventions: (standards)Makefile Conventions.
The `configure' shell script attempts to guess correct values for
various system-dependent variables used during compilation. It uses
those values to create a `Makefile' in each directory of the package.
It may also create one or more `.h' files containing system-dependent
definitions. Finally, it creates a shell script `config.status' that
you can run in the future to recreate the current configuration, and a
file `config.log' containing compiler output (useful mainly for
debugging `configure').
It can also use an optional file (typically called `config.cache'
and enabled with `--cache-file=config.cache' or simply `-C') that saves
the results of its tests to speed up reconfiguring. Caching is
disabled by default to prevent problems with accidental use of stale
cache files.
If you need to do unusual things to compile the package, please try
to figure out how `configure' could check whether to do them, and mail
diffs or instructions to the address given in the `README' so they can
be considered for the next release. If you are using the cache, and at
some point `config.cache' contains results you don't want to keep, you
may remove or edit it.
The file `configure.ac' (or `configure.in') is used to create
`configure' by a program called `autoconf'. You need `configure.ac' if
you want to change it or regenerate `configure' using a newer version
of `autoconf'.
The simplest way to compile this package is:
1. `cd' to the directory containing the package's source code and type
`./configure' to configure the package for your system.
Running `configure' might take a while. While running, it prints
some messages telling which features it is checking for.
2. Type `make' to compile the package.
3. Optionally, type `make check' to run any self-tests that come with
the package, generally using the just-built uninstalled binaries.
4. Type `make install' to install the programs and any data files and
documentation. When installing into a prefix owned by root, it is
recommended that the package be configured and built as a regular
user, and only the `make install' phase executed with root
privileges.
5. Optionally, type `make installcheck' to repeat any self-tests, but
this time using the binaries in their final installed location.
This target does not install anything. Running this target as a
regular user, particularly if the prior `make install' required
root privileges, verifies that the installation completed
correctly.
6. You can remove the program binaries and object files from the
source code directory by typing `make clean'. To also remove the
files that `configure' created (so you can compile the package for
a different kind of computer), type `make distclean'. There is
also a `make maintainer-clean' target, but that is intended mainly
for the package's developers. If you use it, you may have to get
all sorts of other programs in order to regenerate files that came
with the distribution.
7. Often, you can also type `make uninstall' to remove the installed
files again. In practice, not all packages have tested that
uninstallation works correctly, even though it is required by the
GNU Coding Standards.
8. Some packages, particularly those that use Automake, provide `make
distcheck', which can by used by developers to test that all other
targets like `make install' and `make uninstall' work correctly.
This target is generally not run by end users.
Compilers and Options
=====================
Some systems require unusual options for compilation or linking that
the `configure' script does not know about. Run `./configure --help'
for details on some of the pertinent environment variables.
You can give `configure' initial values for configuration parameters
by setting variables in the command line or in the environment. Here
is an example:
./configure CC=c99 CFLAGS=-g LIBS=-lposix
*Note Defining Variables::, for more details.
Compiling For Multiple Architectures
====================================
You can compile the package for more than one kind of computer at the
same time, by placing the object files for each architecture in their
own directory. To do this, you can use GNU `make'. `cd' to the
directory where you want the object files and executables to go and run
the `configure' script. `configure' automatically checks for the
source code in the directory that `configure' is in and in `..'. This
is known as a "VPATH" build.
With a non-GNU `make', it is safer to compile the package for one
architecture at a time in the source code directory. After you have
installed the package for one architecture, use `make distclean' before
reconfiguring for another architecture.
On MacOS X 10.5 and later systems, you can create libraries and
executables that work on multiple system types--known as "fat" or
"universal" binaries--by specifying multiple `-arch' options to the
compiler but only a single `-arch' option to the preprocessor. Like
this:
./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
CPP="gcc -E" CXXCPP="g++ -E"
This is not guaranteed to produce working output in all cases, you
may have to build one architecture at a time and combine the results
using the `lipo' tool if you have problems.
Installation Names
==================
By default, `make install' installs the package's commands under
`/usr/local/bin', include files under `/usr/local/include', etc. You
can specify an installation prefix other than `/usr/local' by giving
`configure' the option `--prefix=PREFIX', where PREFIX must be an
absolute file name.
You can specify separate installation prefixes for
architecture-specific files and architecture-independent files. If you
pass the option `--exec-prefix=PREFIX' to `configure', the package uses
PREFIX as the prefix for installing programs and libraries.
Documentation and other data files still use the regular prefix.
In addition, if you use an unusual directory layout you can give
options like `--bindir=DIR' to specify different values for particular
kinds of files. Run `configure --help' for a list of the directories
you can set and what kinds of files go in them. In general, the
default for these options is expressed in terms of `${prefix}', so that
specifying just `--prefix' will affect all of the other directory
specifications that were not explicitly provided.
The most portable way to affect installation locations is to pass the
correct locations to `configure'; however, many packages provide one or
both of the following shortcuts of passing variable assignments to the
`make install' command line to change installation locations without
having to reconfigure or recompile.
The first method involves providing an override variable for each
affected directory. For example, `make install
prefix=/alternate/directory' will choose an alternate location for all
directory configuration variables that were expressed in terms of
`${prefix}'. Any directories that were specified during `configure',
but not in terms of `${prefix}', must each be overridden at install
time for the entire installation to be relocated. The approach of
makefile variable overrides for each directory variable is required by
the GNU Coding Standards, and ideally causes no recompilation.
However, some platforms have known limitations with the semantics of
shared libraries that end up requiring recompilation when using this
method, particularly noticeable in packages that use GNU Libtool.
The second method involves providing the `DESTDIR' variable. For
example, `make install DESTDIR=/alternate/directory' will prepend
`/alternate/directory' before all installation names. The approach of
`DESTDIR' overrides is not required by the GNU Coding Standards, and
does not work on platforms that have drive letters. On the other hand,
it does better at avoiding recompilation issues, and works well even
when some directory options were not specified in terms of `${prefix}'
at `configure' time.
Optional Features
=================
If the package supports it, you can cause programs to be installed
with an extra prefix or suffix on their names by giving `configure' the
option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
Some packages pay attention to `--enable-FEATURE' options to
`configure', where FEATURE indicates an optional part of the package.
They may also pay attention to `--with-PACKAGE' options, where PACKAGE
is something like `gnu-as' or `x' (for the X Window System). The
`README' should mention any `--enable-' and `--with-' options that the
package recognizes.
For packages that use the X Window System, `configure' can usually
find the X include and library files automatically, but if it doesn't,
you can use the `configure' options `--x-includes=DIR' and
`--x-libraries=DIR' to specify their locations.
Some packages offer the ability to configure how verbose the
execution of `make' will be. For these packages, running `./configure
--enable-silent-rules' sets the default to minimal output, which can be
overridden with `make V=1'; while running `./configure
--disable-silent-rules' sets the default to verbose, which can be
overridden with `make V=0'.
Particular systems
==================
On HP-UX, the default C compiler is not ANSI C compatible. If GNU
CC is not installed, it is recommended to use the following options in
order to use an ANSI C compiler:
./configure CC="cc -Ae -D_XOPEN_SOURCE=500"
and if that doesn't work, install pre-built binaries of GCC for HP-UX.
On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot
parse its `<wchar.h>' header file. The option `-nodtk' can be used as
a workaround. If GNU CC is not installed, it is therefore recommended
to try
./configure CC="cc"
and if that doesn't work, try
./configure CC="cc -nodtk"
On Solaris, don't put `/usr/ucb' early in your `PATH'. This
directory contains several dysfunctional programs; working variants of
these programs are available in `/usr/bin'. So, if you need `/usr/ucb'
in your `PATH', put it _after_ `/usr/bin'.
On Haiku, software installed for all users goes in `/boot/common',
not `/usr/local'. It is recommended to use the following options:
./configure --prefix=/boot/common
Specifying the System Type
==========================
There may be some features `configure' cannot figure out
automatically, but needs to determine by the type of machine the package
will run on. Usually, assuming the package is built to be run on the
_same_ architectures, `configure' can figure that out, but if it prints
a message saying it cannot guess the machine type, give it the
`--build=TYPE' option. TYPE can either be a short name for the system
type, such as `sun4', or a canonical name which has the form:
CPU-COMPANY-SYSTEM
where SYSTEM can have one of these forms:
OS
KERNEL-OS
See the file `config.sub' for the possible values of each field. If
`config.sub' isn't included in this package, then this package doesn't
need to know the machine type.
If you are _building_ compiler tools for cross-compiling, you should
use the option `--target=TYPE' to select the type of system they will
produce code for.
If you want to _use_ a cross compiler, that generates code for a
platform different from the build platform, you should specify the
"host" platform (i.e., that on which the generated programs will
eventually be run) with `--host=TYPE'.
Sharing Defaults
================
If you want to set default values for `configure' scripts to share,
you can create a site shell script called `config.site' that gives
default values for variables like `CC', `cache_file', and `prefix'.
`configure' looks for `PREFIX/share/config.site' if it exists, then
`PREFIX/etc/config.site' if it exists. Or, you can set the
`CONFIG_SITE' environment variable to the location of the site script.
A warning: not all `configure' scripts look for a site script.
Defining Variables
==================
Variables not defined in a site shell script can be set in the
environment passed to `configure'. However, some packages may run
configure again during the build, and the customized values of these
variables may be lost. In order to avoid this problem, you should set
them in the `configure' command line, using `VAR=value'. For example:
./configure CC=/usr/local2/bin/gcc
causes the specified `gcc' to be used as the C compiler (unless it is
overridden in the site shell script).
Unfortunately, this technique does not work for `CONFIG_SHELL' due to
an Autoconf bug. Until the bug is fixed you can use this workaround:
CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash
`configure' Invocation
======================
`configure' recognizes the following options to control how it
operates.
`--help'
`-h'
Print a summary of all of the options to `configure', and exit.
`--help=short'
`--help=recursive'
Print a summary of the options unique to this package's
`configure', and exit. The `short' variant lists options used
only in the top level, while the `recursive' variant lists options
also present in any nested packages.
`--version'
`-V'
Print the version of Autoconf used to generate the `configure'
script, and exit.
`--cache-file=FILE'
Enable the cache: use and save the results of the tests in FILE,
traditionally `config.cache'. FILE defaults to `/dev/null' to
disable caching.
`--config-cache'
`-C'
Alias for `--cache-file=config.cache'.
`--quiet'
`--silent'
`-q'
Do not print messages saying which checks are being made. To
suppress all normal output, redirect it to `/dev/null' (any error
messages will still be shown).
`--srcdir=DIR'
Look for the package's source code in directory DIR. Usually
`configure' can determine that directory automatically.
`--prefix=DIR'
Use DIR as the installation prefix. *note Installation Names::
for more details, including other options available for fine-tuning
the installation locations.
`--no-create'
`-n'
Run the configure checks, but stop before creating any output
files.
`configure' also accepts some other, not widely useful, options. Run
`configure --help' for more details.

81
Makefile.am Normal file
View File

@ -0,0 +1,81 @@
SUBDIRS=ao-tools src doc icon altoslib libaltos altosuilib altosui micropeak ao-utils altosdroid telegps map-server
EXTRA_DIST = ChangeLog
MAINTAINERCLEANFILES = ChangeLog
.PHONY: ChangeLog
ChangeLog:
(GIT_DIR=$(top_srcdir)/.git git log > .changelog.tmp && mv .changelog.tmp ChangeLog; rm -f .changelog.tmp) || \
(touch ChangeLog; echo 'git directory not found: installing possibly empty changelog.' >&2)
dist-hook: ChangeLog
if FATINSTALL
fat-install: fat
cd altosui && $(MAKE) fat-install
cd telegps && $(MAKE) fat-install
cd micropeak && $(MAKE) fat-install
endif
fat: all-recursive
cd libaltos && $(MAKE) fat
cd altoslib && $(MAKE) all
cd icon && $(MAKE) fat
cd altosui && $(MAKE) fat
cd micropeak && $(MAKE) fat
cd telegps && $(MAKE) fat
fat_linux = \
altosui/Altos-Linux-$(VERSION).sh altosui/Altos-Linux-$(VERSION).tar.bz2 \
telegps/TeleGPS-Linux-$(VERSION).sh telegps/TeleGPS-Linux-$(VERSION).tar.bz2 \
micropeak/MicroPeak-Linux-$(VERSION).sh micropeak/MicroPeak-Linux-$(VERSION).tar.bz2
fat_mac = \
altosui/Altos-Mac-$(VERSION).dmg \
telegps/TeleGPS-Mac-$(VERSION).dmg \
micropeak/MicroPeak-Mac-$(VERSION).dmg
fat_windows = \
altosui/Altos-Windows-$(VERSION_DASH).exe \
telegps/TeleGPS-Windows-$(VERSION_DASH).exe \
micropeak/MicroPeak-Windows-$(VERSION_DASH).exe
fat_android = \
altosdroid/bin/AltosDroid-debug.apk \
altosdroid/bin/AltosDroid-release.apk
fat_altos = \
src/easymega-v1.0/easymega-v1.0-$(VERSION).ihx \
src/easymega-v2.0/easymega-v2.0-$(VERSION).ihx \
src/easymini-v1.0/easymini-v1.0-$(VERSION).ihx \
src/easymini-v2.0/easymini-v2.0-$(VERSION).ihx \
src/easymini-v3.0/easymini-v3.0-$(VERSION).ihx \
src/easymotor-v3/easymotor-v3-$(VERSION).ihx \
src/easytimer-v1/easytimer-v1-$(VERSION).ihx \
src/easytimer-v2/easytimer-v2-$(VERSION).ihx \
src/telebt-v3.0/telebt-v3.0-$(VERSION).ihx \
src/telebt-v4.0/telebt-v4.0-$(VERSION).ihx \
src/teledongle-v3.0/teledongle-v3.0-$(VERSION).ihx \
src/telegps-v1.0/telegps-v1.0-$(VERSION).ihx \
src/telegps-v2.0/telegps-v2.0-$(VERSION).ihx \
src/telemega-v1.0/telemega-v1.0-$(VERSION).ihx \
src/telemega-v2.0/telemega-v2.0-$(VERSION).ihx \
src/telemega-v3.0/telemega-v3.0-$(VERSION).ihx \
src/telemega-v4.0/telemega-v4.0-$(VERSION).ihx \
src/telemega-v5.0/telemega-v5.0-$(VERSION).ihx \
src/telemega-v6.0/telemega-v6.0-$(VERSION).ihx \
src/telemetrum-v2.0/telemetrum-v2.0-$(VERSION).ihx \
src/telemetrum-v3.0/telemetrum-v3.0-$(VERSION).ihx \
src/telemetrum-v4.0/telemetrum-v4.0-$(VERSION).ihx \
src/telelco-v2.0/telelco-v2.0-$(VERSION).ihx \
src/telefireeight-v1.0/telefireeight-v1.0-$(VERSION).ihx \
src/telefireeight-v2.0/telefireeight-v2.0-$(VERSION).ihx
keithp-fat: fat
ssh keithp.com mkdir -p public_html/altos-$(VERSION)
scp -p $(fat_linux) $(fat_mac) $(fat_windows) $(fat_android) $(fat_altos) keithp.com:public_html/altos-$(VERSION)
set-java-versions:
$(top_srcdir)/fix-java-versions org.altusmetrum.altoslib=$(ALTOSLIB_VERSION) org.altusmetrum.altosuilib=$(ALTOSUILIB_VERSION)

0
NEWS Normal file
View File

115
README Normal file
View File

@ -0,0 +1,115 @@
AltOS - 8051 operating system for Altus-Metrum projects
Copyright and License
Copyright © 2009 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
Tasking API:
* Multi-tasking
* Non-preemptive
* Unix-style sleep/wakeup scheduling
* Strict round-robin, no priorities
uint8_t ao_sleep(void *wchan)
Puts current task to sleep. Will wake up when wchan is
signalled (returning 0), or when an alarm has expired
(returning 1).
void ao_wakeup(void *wchan)
Wakeup all tasks sleeping on wchan
void ao_add_task(struct ao_task *task, void (*start)(void))
Adds a task to the queue of available tasks
void ao_start_scheduler(void)
Invokes the scheduler, starting the operating system
void ao_yield(void)
Switches to another task which is ready to run. Allows
tasks which want to run for a while to give up the CPU
without needing to sleep
void ao_panic(uint8_t reason)
Any fatal error should call this function, which can
display the error code in some cryptic fashion.
void ao_wake_task(struct ao_task *task)
Wake the task as if the wchan it is waiting on had
been signalled.
void ao_alarm(uint16_t delay)
Queue an alarm to expire 'delay' ticks in the future.
The alarm will cause the task to wake from ao_sleep
and return 1. Alarms are cancelled if the task is
awoken by ao_wakeup or ao_wake_task.
void ao_exit(void)
Stop the current task and remove it from the list of
available tasks.
Timer API
* Regular timer ticks (at 100Hz, by default)
* All time values are in ticks
uint16_t ao_time(void)
Returns the curent tick count
void ao_delay(uint16_t delay)
Suspend task execution for 'delay' ticks.
DMA API
* Static DMA entry allocation
uint8_t ao_dma_alloc(uint8_t *done)
Allocates one of the 5 DMA units on the cc1111
processor. When this DMA unit is finished, it will
set *done to 1 and wakeup anyone waiting on that.
void ao_dma_set_transfer(uint8_t id, void *src, void *dst,
uint16_t count, uint8_t cfg0, uint8_t cfg1)
Prepares the specified DMA unit for operation.
void ao_dma_start(uint8_t id)
Start DMA on the specified channel.
void ao_dma_trigger(uint8_t id)
Manually trigger a DMA channel
void ao_dma_abort(uint8_t id)
Abort a pending DMA transfer, signalling
that by setting the associated 'done' to
AO_DMA_ABORTED and waking any task
sleeping on that.

182
Releasing Normal file
View File

@ -0,0 +1,182 @@
Adding a product to the release
Make sure the firmware and loader get built by default in
src/Makefile
Add firmware targets to fat_altos target in Makefile.am
Add the firmware to altosui/Makefile.am
Add the firmware to altosui/altos-windows.nsi.in
Add the firmware to telegps/Makefile.am
Add the firmware to telegps/telegps-windows.nsi.in
Add the firmware to Releasing
These are Keith's notes on how to do a release
- update the version and date in configure.ac if Bdale hasn't already
- update the ANDROID_VERSION in configure.ac if releasing AltosDroid
- follow instructions in doc/RELNOTES
- make sure there is a doc/release-notes-<version>.inc
- make sure doc/release-notes.inc points at that
- make sure doc/Makefile points at that too
- confirm doc/header.inc has correct copyright year
These are Bdale's notes on how to do a release.
- make sure Debian build environment is up to date
sudo cowbuilder --update
- make sure fat build environment is up to date
sudo apt update && sudo apt upgrade && sudo apt autoremove
- ensure i386 build support is available, and we have tools to build
installers for Windows and Mac OS X
sudo apt update
sudo apt install genisoimage nsis \
gcc-i686-linux-gnu gcc-aarch64-linux-gnu \
gcc-arm-linux-gnueabi gcc-arm-linux-gnueabihf \
gcc-mingw-w64-i686-posix gcc-mingw-w64-x86-64-win32
- make sure jsign is installed so we can sign Windows installers:
https://github.com/ebourg/jsign/releases/download/5.0/jsign_5.0_all.deb
- make sure ~/web/altusmetrum has no pending pullable commits
git checkout master
- update the version in configure.ac if Keith hasn't already
- if this is an x.y release, then:
git checkout -b branch-<version>
- if this is an x.y.z release, then:
git checkout branch-<version> # the x.y parts only
- cherry-pick or merge appropriate content from master
- make sure there is a doc/release-notes-<version>.inc
- make sure that doc/*.txt have the right copyright year and the
new release is included
- confirm doc/header.inc has correct copyright year
- make absolutely sure checked-out tree is "clean"
- make absolutely sure any commits Keith might have pushed to branches
like debian are already pulled
git log > ChangeLog
git commit -a -m'update ChangeLog for <version> release'
git tag -a <version> -m'Releasing <version>' # full x.y.z version
- make sure .git/gbp.conf set to use branch-<version> as upstream
git checkout debian
git merge branch-<version>
- verify debian/changelog is "clean" ending in last release version
- craft a suitable debian/changelog entry, possibly using:
export EMAIL=bdale@gag.com
gbp dch --release --multimaint-merge --new-version=<version>-1
git commit -n debian/changelog -m "update changelog for Debian build"
- if this is a -1 release, then
gbp buildpackage --git-no-pristine-tar \
--git-upstream-branch=branch-<version> \ # eg 1.3
--git-upstream-tag=<version> # eg 1.3.1
pristine-tar commit \
../build-area/altos_<version>.orig.tar.gz \
branch-<version>
else if this is not a -1 release
gbp buildpackage
git tag debian/<version>
- at this point we have packages in ~/debian/build-area/altos, now
we move on to the non-Debian part of the release process
make distclean (just to be sure, this should do nothing)
./autogen.sh --enable-multi-arch \
--with-fat-dir=/home/bdale/web/altusmetrum/
make && make fat
- store a stable copy of ARM binaries for production use
cp src/chaoskey-v1.0/{*.elf,*.ihx,*.bin,*.map} \
src/easymega-v[1-2].0/{*.elf,*.ihx,*.map} \
src/easymini-v[1-3].0/{*.elf,*.ihx,*.map} \
src/easymotor-v3/{*.elf,*.ihx,*.map} \
src/easytimer-v1/{*.elf,*.ihx,*.map} \
src/easytimer-v2/{*.elf,*.ihx,*.map} \
src/telebt-v[3-4].0/{*.elf,*.ihx,*.map} \
src/teledongle-v3.0/{*.elf,*.ihx,*.map} \
src/telegps-v[1-3].0/{*.elf,*.ihx,*.map} \
src/telemega-v[1-6].0/{*.elf,*.ihx,*.map} \
src/telemetrum-v[2-4].0/{*.elf,*.ihx,*.map} \
src/telemini-v3.0/{*.elf,*.ihx,*.map} \
src/telelco-v2.0/{*.elf,*.ihx,*.map} \
src/telefireeight-v[1-2].0/{*.elf,*.ihx,*.map} \
~/altusmetrumllc/Binaries/
cp src/chaoskey-v1.0/flash-loader/{*.elf,*.bin,*.map} \
src/easymega-v[1-2].0/flash-loader/*.elf \
src/easymini-v[1-3].0/flash-loader/*.elf \
src/easymotor-v3/flash-loader/*.elf \
src/easytimer-v1/flash-loader/*.elf \
src/easytimer-v2/flash-loader/*.elf \
src/telebt-v[3-4].0/flash-loader/{*.elf,*.bin,*.map} \
src/teledongle-v3.0/flash-loader/*.elf \
src/telegps-v[1-3].0/flash-loader/{*.elf,*.bin,*.map} \
src/telemega-v[1-6].0/flash-loader/*.elf \
src/telemetrum-v[2-4].0/flash-loader/*.elf \
src/telemini-v3.0/flash-loader/{*.elf,*.bin,*.map} \
src/telelco-v2.0/flash-loader/*.elf \
src/telefireeight-v[1-2].0/flash-loader/*.elf \
~/altusmetrumllc/Binaries/loaders/
(cd ~/altusmetrumllc ; git add Binaries ; git commit -a)
- remove previous versions (only keep latest release)
(cd ~/altusmetrumllc ; git push)
- Push new release to web site
make fat-install
(cd doc ; make publish)
(cd ~/web/altusmetrum/ && \
git add */releases && git commit -m'Release <rev>' && \
git push origin master)
- upload the Debian package
- clean up
sudo debian/rules clean
git push origin master branch-<version> debian pristine-tar
git push --tags
Testing before a release
To verify that a build works, the following need to be checked
on each platform:
1) Install package
2) Connect TM *and* TD devices. Verify that you can Monitor
Flight from the TD and Configure Telemetrum from the TM.
3) Replay Flight, using your favorite .eeprom file. Check
each tab, especially the 'Site Map' tab. Make sure the
sound works.
4) Graph Data. Graph a favorite .eeprom file. Make sure you
can zoom in on some region of the graph.

11
altosdroid/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
.gradle
.idea
build/
app/build/
app/libs/
app/app.iml
altosdroid.iml
local.properties
app/src/main/AndroidManifest.xml
app/src/main/java/org/altusmetrum/AltosDroid/BuildInfo.java
app/src/main/res/drawable/*led.png

43
altosdroid/.project Normal file
View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>AltosDroid</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>auto,full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/Generate BuildInfo.java.launch</value>
</dictionary>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

106
altosdroid/Makefile.am Normal file
View File

@ -0,0 +1,106 @@
DBG_APK=app/build/outputs/apk/debug/app-debug.apk
REL_APK=app/build/outputs/apk/release/app-release.apk
if ANDROID
all_target=$(DBG_APK)
all_target += bin/AltosDroid-debug.apk
if ANDROID_RELEASE
all_target+=$(REL_APK)
all_target += bin/AltosDroid-release.apk
endif
else
all_target=
endif
SDK=$(ANDROID_SDK)
SDK_TARGET=$(shell sed -ne 's/^target=//p' project.properties)
DX=$(SDK)/platform-tools/dx
ADB=$(SDK)/platform-tools/adb
AAPT=$(SDK)/platform-tools/aapt
APKBUILDER=$(SDK)/tools/apkbuilder
ZIPALIGN_A=$(SDK)/tools/zipalign
ZIPALIGN_B=$(shell ls $(SDK)/build-tools/*/zipalign | tail -1)
JAVA_SRC_DIR=app/src/main/java/org/altusmetrum/AltosDroid
EXT_LIBDIR=app/libs
RES_DIR=app/src/main/res
DRAWABLE_DIR=$(RES_DIR)/drawable
LAYOUT_DIR=$(RES_DIR)/layout
MENU_DIR=$(RES_DIR)/menu
VALUES_DIR=$(RES_DIR)/values
XML_DIR=$(RES_DIR)/xml
ALTOSLIB_SRCDIR=../altoslib
ALTOSLIB_JAR=altoslib_$(ALTOSLIB_VERSION).jar
ALTOSLIB=$(EXT_LIBDIR)/$(ALTOSLIB_JAR)
JAVA_SRC=$(JAVA_SRC_DIR)/*.java $(JAVA_SRC_DIR)/BuildInfo.java
DRAWABLES=\
$(DRAWABLE_DIR)/redled.png \
$(DRAWABLE_DIR)/greenled.png \
$(DRAWABLE_DIR)/grayled.png
GRADLEW=JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 bash ./gradlew --no-daemon
LAYOUTS=$(LAYOUT_DIR)/*.xml
MENUS=$(MENU_DIR)/*.xml
VALUES=$(VALUES_DIR)/*.xml
XMLS=$(XML_DIR)/*.xml app/src/main/AndroidManifest.xml
RES=$(LAYOUTS) $(MENUS) $(VALUES) $(XMLS)
SRC=$(JAVA_SRC) $(DRAWABLES) $(RES)
all: $(all_target)
bin/AltosDroid-debug.apk: $(DBG_APK)
mkdir -p bin
cp $^ $@
bin/AltosDroid-release.apk: $(REL_APK)
mkdir -p bin
cp $^ $@
.NOTPARALLEL:
$(ALTOSLIB): $(ALTOSLIB_SRCDIR)/$(ALTOSLIB_JAR)
mkdir -p $(EXT_LIBDIR)
cd $(EXT_LIBDIR) && ln -sf $(shell echo $(EXT_LIBDIR) | sed 's|[^/]\+|..|g')/$(ALTOSLIB_SRCDIR)/$(ALTOSLIB_JAR) .
$(JAVA_SRC_DIR)/BuildInfo.java: $(filter-out $(JAVA_SRC_DIR)/BuildInfo.java,$(shell echo $(JAVA_SRC)))
./buildinfo.sh
$(DRAWABLE_DIR)/%.png: ../icon/%.png
cd $(DRAWABLE_DIR) && ln -sf $(shell echo $(DRAWABLE_DIR) | sed 's|[^/]\+|..|g')/$< .
if ANDROID
install-release: $(REL_APK)
$(ADB) install -r $(REL_APK)
install-debug: $(DBG_APK)
$(ADB) install -r $(DBG_APK)
$(DBG_APK): $(SRC) $(ALTOSLIB)
$(GRADLEW) assembleDebug
$(REL_APK): $(SRC) $(ALTOSLIB)
$(GRADLEW) build
release: $(REL_APK)
clean-local: $(GOOGLE_PLAY_SERVICES_LIB)
$(GRADLEW) clean
rm -f $(JAVA_SRC_DIR)/BuildInfo.java
rm -f $(DRAWABLES)
rm -rf $(EXT_LIBDIR)
rm -rf $(GOOGLE_PLAY_SERVICES_LIB)
else
clean-local:
endif
clean: clean-local

108
altosdroid/Notebook Normal file
View File

@ -0,0 +1,108 @@
Desired AltosDroid feature list
*) GPS satellite status tab. Monitor GPS C/N0 numbers and
SVIDs. Provides more info before GPS lock is acquired.
*) Channel scanning. Provide the ability to search for telemetry
signals like AltosUI does.
*) Random frequency selection. Provide some mechanism to input
arbitrary radio frequencies. Could be like AltosUI which allows
you to edit the list of frequencies and assign names to them,
or perhaps something better.
*) TM configuration from the droid
*) Monitor-idle mode
*) Online maps comes up tracking object at 0,0
*) Have names for each serial number, default to callsign
Completed features
*) Highlight current frequency in the frequency list.
Placed current frequency in title bar
*) Remember most-recently-used TBT and frequency, perhaps
auto-connect at startup.
Done
*) Re-loading flight data from .telem file to get back to
'find my rocket' mode after shutting down the application.
Done
*) Imperial Units mode
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.

1
altosdroid/app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build.gradle

View File

@ -0,0 +1,47 @@
apply plugin: 'com.android.application'
def keystorePropertiesFile = file(System.properties['user.home'] + "/altusmetrumllc/android_keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
signingConfigs {
release {
storeFile file(System.properties['user.home'] + "/altusmetrumllc/android_keystore.jks")
storePassword keystoreProperties['storePassword']
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
}
}
compileSdkVersion 28
defaultConfig {
applicationId "org.altusmetrum.AltosDroid"
minSdkVersion 21
targetSdkVersion 31
versionCode @ANDROID_VERSION@
versionName "@VERSION@"
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
debuggable false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
}
}
lintOptions {
abortOnError false
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.core:core:1.2.0'
implementation 'com.google.android.gms:play-services-maps:17.0.0'
implementation fileTree(dir: 'libs', include: ['*.jar'])
}

21
altosdroid/app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2012 Mike Beattie <mike@ethernal.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.altusmetrum.AltosDroid">
<!-- Google Maps -->
<uses-feature android:glEsVersion="0x00020000" android:required="true"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Permissions needed to access bluetooth -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- Permissions needed to save Telemetry logs to SD card -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Permissions needed for GoogleMaps -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<permission android:name="org.altusmetrum.AltosDroid.permission.MAPS_RECEIVE"
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" android:required="false" />
<application android:label="@string/app_name"
android:icon="@drawable/app_icon"
android:allowBackup="true"
android:theme="@style/Medium">
<activity android:name="org.altusmetrum.AltosDroid.AltosDroid"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleTop"
android:exported="true">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<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=".SelectTrackerActivity"
android:label="@string/select_tracker"
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" />
<activity android:name=".IdleModeActivity"
android:label="@string/idle_mode"
android:theme="@android:style/Theme.Dialog"
android:configChanges="orientation|keyboardHidden" />
<activity android:name=".IgniterActivity"
android:label="@string/igniters"
android:theme="@android:style/Theme.Dialog"
android:configChanges="orientation|keyboardHidden" />
<activity android:name=".SetupActivity"
android:label="@string/setup"
android:theme="@android:style/Theme.Dialog"
android:configChanges="orientation" />
<activity android:name=".ManageFrequenciesActivity"
android:label="@string/manage_frequencies"
android:theme="@android:style/Theme.Dialog"
android:configChanges="orientation|keyboard" />
<service android:name=".TelemetryService" />
<meta-data android:name="com.google.android.maps.v2.API_KEY"
android:value="@GOOGLEKEY@"/>
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
</application>
</manifest>

View File

@ -0,0 +1,224 @@
/*
* Copyright © 2011 Keith Packard <keithp@keithp.com>
* Copyright © 2012 Mike Beattie <mike@ethernal.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
public class AltosBluetooth extends AltosDroidLink {
private ConnectThread connect_thread = null;
private BluetoothDevice device;
private BluetoothSocket socket;
private InputStream input;
private OutputStream output;
private boolean pause;
// Constructor
public AltosBluetooth(BluetoothDevice device, Handler handler, boolean pause) {
super(handler);
this.device = device;
this.handler = handler;
this.pause = pause;
connect_thread = new ConnectThread();
connect_thread.start();
}
void connected() {
if (closed()) {
AltosDebug.debug("connected after closed");
return;
}
AltosDebug.check_ui("connected\n");
try {
synchronized(this) {
if (socket != null) {
input = socket.getInputStream();
output = socket.getOutputStream();
super.connected();
}
}
} catch (InterruptedException ie) {
connect_failed();
} catch (IOException io) {
connect_failed();
}
}
private void connect_failed() {
if (closed()) {
AltosDebug.debug("connect_failed after closed");
return;
}
close_device();
input = null;
output = null;
handler.obtainMessage(TelemetryService.MSG_CONNECT_FAILED, this).sendToTarget();
AltosDebug.error("ConnectThread: Failed to establish connection");
}
void close_device() {
BluetoothSocket tmp_socket;
synchronized(this) {
tmp_socket = socket;
socket = null;
}
if (tmp_socket != null) {
try {
tmp_socket.close();
} catch (IOException e) {
AltosDebug.error("close_socket failed");
}
}
}
public void close() {
super.close();
input = null;
output = null;
}
private final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private void create_socket(BluetoothDevice device) {
BluetoothSocket tmp_socket = null;
AltosDebug.check_ui("create_socket\n");
try {
tmp_socket = device.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
} catch (IOException e) {
e.printStackTrace();
}
if (socket != null) {
AltosDebug.debug("Socket already allocated %s", socket.toString());
close_device();
}
synchronized (this) {
socket = tmp_socket;
}
}
private class ConnectThread extends Thread {
public void run() {
AltosDebug.debug("ConnectThread: BEGIN (pause %b)", pause);
setName("ConnectThread");
if (pause) {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
}
}
create_socket(device);
// Always cancel discovery because it will slow down a connection
try {
BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
} catch (Exception e) {
AltosDebug.debug("cancelDiscovery exception %s", e.toString());
}
BluetoothSocket local_socket = null;
synchronized (AltosBluetooth.this) {
if (!closed())
local_socket = socket;
}
if (local_socket != null) {
try {
// 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();
}
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();
}
int write(byte[] buffer, int len) {
if (output == null)
return -1;
try {
output.write(buffer, 0, len);
} catch (IOException ie) {
return -1;
}
return len;
}
int read(byte[] buffer, int len) {
if (input == null)
return -1;
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; }
public boolean show_reply_timeout() { return true; }
public void hide_reply_timeout() { }
}

View File

@ -0,0 +1,69 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.lang.*;
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 trace(String format, Object ... arguments) {
error(format, arguments);
for (StackTraceElement el : Thread.currentThread().getStackTrace())
Log.e(TAG, "\t" + el.toString() + "\n");
}
static void check_ui(String format, Object ... arguments) {
if (Looper.myLooper() == Looper.getMainLooper())
trace("ON UI THREAD " + format, arguments);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,214 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import android.os.Handler;
import org.altusmetrum.altoslib_14.*;
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(data.replace('\n', '\\'));
for (byte b : bytes)
putchar(b);
}
public AltosDroidLink(Handler handler) {
this.handler = handler;
}
}

View File

@ -0,0 +1,34 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import android.location.Location;
import org.altusmetrum.altoslib_14.*;
public interface AltosDroidMapInterface {
public void onCreateView(AltosDroid altos_droid);
public void onDestroyView();
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);
}

View File

@ -0,0 +1,23 @@
/*
* Copyright © 2016 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
public interface AltosDroidMapSourceListener {
public void map_source_changed(int map_source);
}

View File

@ -0,0 +1,186 @@
/*
* Copyright © 2014 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.util.*;
import android.content.Context;
import org.altusmetrum.altoslib_14.*;
public class AltosDroidPreferences extends AltosPreferences {
/* Active device preference name */
final static String activeDeviceAddressPreference = "ACTIVE-DEVICE-ADDRESS";
final static String activeDeviceNamePreference = "ACTIVE-DEVICE-NAME";
public static final int font_size_small = 0;
public static final int font_size_medium = 1;
public static final int font_size_large = 2;
public static final int font_size_extra = 3;
final static String fontSizePreference = "FONT-SIZE";
static int font_size = font_size_medium;
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;
/* Tracker sort selection */
final static String trackerSortPreference = "TRACKER-SORT";
static int tracker_sort;
public static void init(Context context) {
if (backend != null)
return;
AltosPreferences.init(new AltosDroidPreferencesBackend(context));
font_size = backend.getInt(fontSizePreference, font_size_medium);
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);
tracker_sort = backend.getInt(trackerSortPreference, 0);
}
public static void set_active_device(DeviceAddress address) {
if (backend == null)
return;
synchronized(backend) {
active_device_address = address;
if (active_device_address != null) {
backend.putString(activeDeviceAddressPreference, active_device_address.address);
backend.putString(activeDeviceNamePreference, active_device_address.name);
} else {
backend.remove(activeDeviceAddressPreference);
backend.remove(activeDeviceNamePreference);
}
flush_preferences();
}
}
public static DeviceAddress active_device() {
if (backend == null)
return null;
synchronized(backend) {
return active_device_address;
}
}
static LinkedList<AltosDroidMapSourceListener> map_source_listeners;
public static void set_map_source(int map_source) {
if (backend == null)
return;
synchronized(backend) {
AltosDroidPreferences.map_source = map_source;
backend.putInt(mapSourcePreference, map_source);
flush_preferences();
}
if (map_source_listeners != null) {
for (AltosDroidMapSourceListener l : map_source_listeners) {
l.map_source_changed(map_source);
}
}
}
public static int map_source() {
if (backend == null)
return MAP_SOURCE_ONLINE;
synchronized(backend) {
return map_source;
}
}
public static void register_map_source_listener(AltosDroidMapSourceListener l) {
if (backend == null)
return;
synchronized(backend) {
if (map_source_listeners == null)
map_source_listeners = new LinkedList<AltosDroidMapSourceListener>();
map_source_listeners.add(l);
}
}
public static void unregister_map_source_listener(AltosDroidMapSourceListener l) {
if (backend == null)
return;
synchronized(backend) {
map_source_listeners.remove(l);
}
}
public static int font_size() {
if (backend == null)
return font_size_medium;
synchronized (backend) {
return font_size;
}
}
public static void set_font_size(int new_font_size) {
if (backend == null)
return;
synchronized (backend) {
if (font_size != new_font_size) {
font_size = new_font_size;
backend.putInt(fontSizePreference, font_size);
flush_preferences();
}
}
}
public static int tracker_sort() {
if (backend == null)
return 0;
synchronized(backend) {
return tracker_sort;
}
}
public static void set_tracker_sort(int new_tracker_sort) {
if (backend == null)
return;
synchronized(backend) {
if (tracker_sort != new_tracker_sort) {
tracker_sort = new_tracker_sort;
backend.putInt(trackerSortPreference, tracker_sort);
flush_preferences();
}
}
}
}

View File

@ -0,0 +1,147 @@
/*
* Copyright © 2012 Mike Beattie <mike@ethernal.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.io.File;
import java.util.Map;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Environment;
import android.util.*;
import org.altusmetrum.altoslib_14.*;
public class AltosDroidPreferencesBackend extends AltosPreferencesBackend {
public final static String NAME = "org.altusmetrum.AltosDroid";
private Context context = null;
private SharedPreferences prefs = null;
private SharedPreferences.Editor editor = null;
public AltosDroidPreferencesBackend(Context in_context) {
this(in_context, NAME);
}
public AltosDroidPreferencesBackend(Context in_context, String in_prefs) {
context = in_context;
prefs = context.getSharedPreferences(in_prefs, 0);
editor = prefs.edit();
}
public String[] keys() {
Map<String, ?> all = prefs.getAll();
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) {
if (!nodeExists(key))
putBoolean(key, true);
return new AltosDroidPreferencesBackend(context, key);
}
public boolean nodeExists(String key) {
return prefs.contains(key);
}
public boolean getBoolean(String key, boolean def) {
return prefs.getBoolean(key, def);
}
public double getDouble(String key, double def) {
Float f = Float.valueOf(prefs.getFloat(key, (float)def));
return f.doubleValue();
}
public int getInt(String key, int def) {
return prefs.getInt(key, def);
}
public String getString(String key, String def) {
String ret;
if (key.equals(AltosPreferences.logdirPreference))
ret = null;
else
ret = prefs.getString(key, def);
AltosDebug.debug("AltosDroidPreferencesBackend get string %s:\n", key);
if (ret == null)
AltosDebug.debug(" (null)\n");
else {
String[] lines = ret.split("\n");
for (String l : lines)
AltosDebug.debug(" %s\n", l);
}
return ret;
}
public byte[] getBytes(String key, byte[] def) {
String save = prefs.getString(key, null);
if (save == null)
return def;
byte[] bytes = Base64.decode(save, Base64.DEFAULT);
return bytes;
}
public void putBoolean(String key, boolean value) {
editor.putBoolean(key, value);
}
public void putDouble(String key, double value) {
editor.putFloat(key, (float)value);
}
public void putInt(String key, int value) {
editor.putInt(key, value);
}
public void putString(String key, String value) {
// AltosDebug.debug("AltosDroidPreferencesBackend put string %s:\n", key);
// String[] lines = value.split("\n");
// for (String l : lines)
// AltosDebug.debug(" %s\n", l);
editor.putString(key, value);
}
public void putBytes(String key, byte[] bytes) {
String save = Base64.encodeToString(bytes, Base64.DEFAULT);
editor.putString(key, save);
}
public void remove(String key) {
AltosDebug.debug("remove preference %s\n", key);
editor.remove(key);
}
public void flush() {
editor.apply();
}
public File homeDirectory() {
return context.getExternalMediaDirs()[0];
}
public void debug(String format, Object ... arguments) {
AltosDebug.debug(format, arguments);
}
}

View File

@ -0,0 +1,105 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import org.altusmetrum.altoslib_14.*;
import android.location.Location;
import android.app.Activity;
import android.content.*;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
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(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver);
public abstract String tab_name();
public void units_changed(boolean imperial_units) {
if (!isHidden())
show(last_telem_state, last_state, last_from_receiver, last_receiver);
}
public void set_value(TextView text_view,
AltosUnits units,
int width,
double value) {
if (value == AltosLib.MISSING)
text_view.setText("");
else
text_view.setText(units.show(width, value));
}
public void set_visible(boolean visible) {
FragmentTransaction ft = AltosDroid.fm.beginTransaction();
AltosDebug.debug("set visible %b %s\n", visible, tab_name());
if (visible) {
ft.show(this);
show(last_telem_state, last_state, last_from_receiver, last_receiver);
} else
ft.hide(this);
try {
ft.commitAllowingStateLoss();
} catch (IllegalStateException ie) {
}
}
@Override
public void onAttach(Context context) {
AltosDebug.debug("tab onAttach %s %s\n", tab_name(), this);
super.onAttach(context);
altos_droid = (AltosDroid) context;
altos_droid.registerTab(this);
}
@Override
public void onDetach() {
AltosDebug.debug("tab onDetach %s %s\n", tab_name(), this);
super.onDetach();
altos_droid.unregisterTab(this);
altos_droid = null;
}
@Override
public void onResume() {
super.onResume();
AltosDebug.debug("onResume tab %s %s\n", tab_name(), this);
set_visible(true);
}
public void update_ui(TelemetryState telem_state, AltosState state,
AltosGreatCircle from_receiver, Location receiver, boolean is_current)
{
AltosDebug.debug("update_ui %s is_current %b\n", tab_name(), is_current);
last_telem_state = telem_state;
last_state = state;
last_from_receiver = from_receiver;
last_receiver = receiver;
if (is_current)
show(telem_state, state, from_receiver, receiver);
else
return;
}
}

View File

@ -0,0 +1,527 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.util.*;
import java.io.*;
import org.altusmetrum.altoslib_14.*;
import android.graphics.*;
import android.view.*;
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, AltosMapTypeListener {
ScaleGestureDetector scale_detector;
boolean scaling;
AltosMap map;
AltosDroid altos_droid;
static int scale = 1;
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 = this.get_image();
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.fetching:
message = "Fetching...";
break;
case AltosMapTile.bad_request:
message = "Internal error";
break;
case AltosMapTile.failed:
message = "Network error";
break;
case AltosMapTile.forbidden:
message = "Outside of known launch areas";
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(AltosMapCache cache, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
super(cache, upper_left, center, zoom, maptype, px_size, scale);
}
}
public AltosMapTile new_tile(AltosMapCache cache, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
return new MapTile(cache, upper_left, center, zoom, maptype, px_size, scale);
}
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);
}
MapMark(double lat, double lon, int state, String label) {
super(lat, lon, state, label);
}
}
public AltosMapMark new_mark(double lat, double lon, int state) {
return new MapMark(lat, lon, state);
}
public AltosMapMark new_mark(double lat, double lon, int state, String label) {
return new MapMark(lat, lon, state, label);
}
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
protected void onDraw(Canvas view_canvas) {
if (map == null) {
debug("MapView draw without map\n");
return;
}
if (map.transform == null) {
debug("MapView draw without transform\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) {
boolean changed = false;
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.keySet().toArray(new Integer[0]);
/* remove deleted keys */
for (int serial : old_serial) {
if (!telem_state.containsKey(serial))
rockets.remove(serial);
}
/* set remaining keys */
for (int serial : new_serial) {
Rocket rocket;
AltosState t_state = telem_state.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 != null && state.cal_data().serial == serial)
there = latlon;
}
if (state != null)
rocket.set_active(state.cal_data().serial == serial);
}
}
if (receiver != null) {
AltosLatLon new_here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
if (!new_here.equals(here)) {
here = new_here;
AltosDebug.debug("Location changed, redraw");
repaint();
}
}
}
public void onCreateView(AltosDroid altos_droid) {
this.altos_droid = altos_droid;
map = new AltosMap(this, scale);
AltosPreferences.register_map_type_listener(this);
map.set_maptype(AltosPreferences.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 onDestroyView() {
AltosPreferences.unregister_map_type_listener(this);
}
public void map_type_changed(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);
}
}

View File

@ -0,0 +1,358 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.util.*;
import org.altusmetrum.altoslib_14.*;
import com.google.android.gms.maps.*;
import com.google.android.gms.maps.model.*;
import android.graphics.Color;
import android.graphics.*;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
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, OnMapReadyCallback, AltosMapTypeListener {
public AltosOnlineMapFragment 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 double mapAccuracy = -1;
private AltosLatLon my_position = null;
private AltosLatLon target_position = null;
private AltosDroid altos_droid;
public static class AltosOnlineMapFragment extends SupportMapFragment {
AltosMapOnline c;
View map_view;
public AltosOnlineMapFragment(AltosMapOnline c) {
this.c = c;
}
public AltosOnlineMapFragment() {
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (c != null)
getMapAsync(c);
}
@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 set_visible(boolean visible) {
if (map_view == null)
return;
if (visible)
map_view.setVisibility(View.VISIBLE);
else
map_view.setVisibility(View.GONE);
}
}
public void onCreateView(AltosDroid altos_droid) {
this.altos_droid = altos_droid;
AltosPreferences.register_map_type_listener(this);
mMapFragment = new AltosOnlineMapFragment(this);
}
public void onDestroyView() {
AltosPreferences.unregister_map_type_listener(this);
}
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() {
synchronized(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;
}
void
position_permission() {
if (mMap != null)
mMap.setMyLocationEnabled(true);
}
@Override
public void onMapReady(GoogleMap googleMap) {
final int map_type = AltosPreferences.map_type();
mMap = googleMap;
if (mMap != null) {
map_type_changed(map_type);
if (altos_droid.have_location_permission)
mMap.setMyLocationEnabled(true);
else
altos_droid.tell_map_permission(this);
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;
synchronized(rockets) {
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) {
synchronized(rockets) {
RocketOnline rocket = rockets.get(serial);
rocket.remove();
rockets.remove(serial);
}
}
public void set_visible(boolean visible) {
if (mMapFragment != null)
mMapFragment.set_visible(visible);
}
public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
if (telem_state != null) {
synchronized(rockets) {
for (int serial : rockets.keySet()) {
if (!telem_state.containsKey(serial))
remove_rocket(serial);
}
for (int serial : telem_state.keySet()) {
set_rocket(serial, telem_state.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 && state.gps.lat != AltosLib.MISSING) {
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 map_type_changed(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;
}
}

View File

@ -0,0 +1,233 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import android.content.Context;
import android.hardware.usb.*;
import android.app.*;
import android.os.Handler;
import org.altusmetrum.altoslib_14.*;
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_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 ||
have_product == AltosLib.product_easytimer;
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) {
if (connection == null)
return 0;
int ret = connection.bulkTransfer(in, buffer, len, -1);
AltosDebug.debug("read(%d) = %d\n", len, ret);
return ret;
}
int write(byte[] buffer, int len) {
if (connection == null)
return 0;
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() { }
}

View File

@ -0,0 +1,53 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import android.content.Context;
import androidx.viewpager.widget.ViewPager;
import android.util.AttributeSet;
import android.view.View;
public class AltosViewPager extends ViewPager {
public AltosViewPager(Context context) {
super(context);
}
public AltosViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int 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);
}
}

View File

@ -0,0 +1,329 @@
/*
* Copyright © 2011 Keith Packard <keithp@keithp.com>
* Copyright © 2012 Mike Beattie <mike@ethernal.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.location.Location;
import org.altusmetrum.altoslib_14.*;
public class AltosVoice {
private TextToSpeech tts = null;
private boolean tts_enabled = false;
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;
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;
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 boolean quiet = false;
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;
}
});
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();
if (!quiet)
tts.speak(s, TextToSpeech.QUEUE_ADD, null, 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.stop();
tts.shutdown();
}
}
private boolean last_apogee_good;
private boolean last_main_good;
private boolean last_gps_good;
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;
}
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;
}
return true;
}
private boolean tell_flight(TelemetryState telem_state, AltosState state,
AltosGreatCircle from_receiver, Location receiver) {
boolean spoken = false;
if (state == null)
return false;
if (last_tell_mode != TELL_MODE_FLIGHT)
last_flight_tell = TELL_FLIGHT_NONE;
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 (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 {
speed = state.gps_speed();
if (speed == AltosLib.MISSING)
speed = state.speed();
}
if (speed != AltosLib.MISSING) {
speak("speed: %s.", AltosConvert.speed.say_units(speed));
return true;
}
}
if (last_flight_tell == TELL_FLIGHT_SPEED) {
last_flight_tell = TELL_FLIGHT_HEIGHT;
height = state.height();
if (height != AltosLib.MISSING) {
speak("height: %s.", AltosConvert.height.say_units(height));
return true;
}
}
if (last_flight_tell == TELL_FLIGHT_HEIGHT) {
last_flight_tell = TELL_FLIGHT_TRACK;
if (from_receiver != null) {
speak("bearing %s %d, elevation %d, distance %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.distance));
return true;
}
}
return spoken;
}
private boolean tell_recover(TelemetryState telem_state, AltosState state,
AltosGreatCircle from_receiver, Location receiver) {
if (from_receiver == null)
return false;
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, distance %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 quiet) {
this.quiet = quiet;
boolean spoken = false;
if (!tts_enabled) return;
if (is_speaking()) return;
int tell_serial = last_tell_serial;
if (state != null)
tell_serial = state.cal_data().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;
}
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright © 2012 Mike Beattie <mike@ethernal.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
public class BuildInfo {
public static final String version = "@VERSION@";
public static final String git_describe = "@DESCRIBE@";
public static final String branch = "@BRANCH@";
public static final String commitnum = "@COMMITNUM@";
public static final String commithash = "@COMMITHASH@";
public static final String builddate = "@BUILDDATE@";
public static final String buildtime = "@BUILDTIME@";
public static final String buildtz = "@BUILDTZ@";
}

View File

@ -0,0 +1,29 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 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;
}
}

View File

@ -0,0 +1,224 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.altusmetrum.AltosDroid;
import java.util.Set;
import 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.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
/**
* This Activity appears as a dialog. It lists any paired devices and
* devices detected in the area after discovery. When a device is chosen
* by the user, the MAC address of the device is sent back to the parent
* Activity in the result Intent.
*/
public class DeviceListActivity extends Activity {
// Return Intent extra
public static final String EXTRA_DEVICE_ADDRESS = "device_address";
public static final String EXTRA_DEVICE_NAME = "device_name";
// Member fields
private BluetoothAdapter mBtAdapter;
private ArrayAdapter<String> mPairedDevicesArrayAdapter;
private ArrayAdapter<String> mNewDevicesArrayAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
// Setup the window
setContentView(R.layout.device_list);
// Set result CANCELED incase the user backs out
setResult(Activity.RESULT_CANCELED);
// Initialize the button to perform device discovery
Button scanButton = (Button) findViewById(R.id.button_scan);
scanButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
doDiscovery();
v.setVisibility(View.GONE);
}
});
// Initialize array adapters. One for already paired devices and
// one for newly discovered devices
mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
// Find and set up the ListView for paired devices
ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
pairedListView.setAdapter(mPairedDevicesArrayAdapter);
pairedListView.setOnItemClickListener(mDeviceClickListener);
// Find and set up the ListView for newly discovered devices
ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);
newDevicesListView.setAdapter(mNewDevicesArrayAdapter);
newDevicesListView.setOnItemClickListener(mDeviceClickListener);
// Register for broadcasts when a device is discovered
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, filter);
// Register for broadcasts when discovery has finished
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, filter);
// Get the local Bluetooth adapter
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
// Get a set of currently paired devices
Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
// If there are paired devices, add each one to the ArrayAdapter
if (pairedDevices.size() > 0) {
findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
for (BluetoothDevice device : pairedDevices) {
String name = device.getName();
if (name != null && name.startsWith("TeleBT"))
mPairedDevicesArrayAdapter.add(name + "\n" + device.getAddress());
}
} else {
String noDevices = getResources().getText(R.string.none_paired).toString();
mPairedDevicesArrayAdapter.add(noDevices);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// Make sure we're not doing discovery anymore
if (mBtAdapter != null) {
mBtAdapter.cancelDiscovery();
}
// Unregister broadcast listeners
this.unregisterReceiver(mReceiver);
}
/**
* Start device discover with the BluetoothAdapter
*/
private void doDiscovery() {
AltosDebug.debug("doDiscovery()");
// Indicate scanning in the title
setTitle(R.string.scanning);
// Turn on sub-title for new devices
findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);
// If we're already discovering, stop it
if (mBtAdapter.isDiscovering()) {
mBtAdapter.cancelDiscovery();
}
// Request discover from BluetoothAdapter
mBtAdapter.startDiscovery();
}
// The on-click listener for all devices in the ListViews
private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {
// Get the device MAC address, which is the last 17 chars in the View
String info = ((TextView) v).getText().toString();
/* Ignore clicks on items that are too short */
if (info.length() <= 17)
return;
// Cancel discovery because it's costly and we're about to connect
mBtAdapter.cancelDiscovery();
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);
finish();
}
};
// The BroadcastReceiver that listens for discovered devices and
// changes the title when discovery is finished
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
/* Get the BluetoothDevice object from the Intent
*/
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
/* If it's already paired, skip it, because it's been listed already
*/
if (device != null && device.getBondState() != BluetoothDevice.BOND_BONDED)
{
String name = device.getName();
if (name != null && name.startsWith("TeleBT"))
mNewDevicesArrayAdapter.add(name + "\n" + device.getAddress());
}
/* When discovery is finished, change the Activity title
*/
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
setTitle(R.string.select_device);
if (mNewDevicesArrayAdapter.getCount() == 0) {
String noDevices = getResources().getText(R.string.none_found).toString();
mNewDevicesArrayAdapter.add(noDevices);
}
}
}
};
}

View File

@ -0,0 +1,183 @@
package org.altusmetrum.AltosDroid;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.HashMap;
public class Dumper {
private static Dumper instance = new Dumper();
protected static Dumper getInstance() {
return instance;
}
class DumpContext {
int maxDepth = 0;
int maxArrayElements = 0;
int callCount = 0;
HashMap<String, String> ignoreList = new HashMap<String, String>();
HashMap<Object, Integer> visited = new HashMap<Object, Integer>();
}
public static String dump(Object o) {
return dump(o, 0, 0, null);
}
public static String dump(Object o, int maxDepth, int maxArrayElements, String[] ignoreList) {
DumpContext ctx = Dumper.getInstance().new DumpContext();
ctx.maxDepth = maxDepth;
ctx.maxArrayElements = maxArrayElements;
if (ignoreList != null) {
for (int i = 0; i < Array.getLength(ignoreList); i++) {
int colonIdx = ignoreList[i].indexOf(':');
if (colonIdx == -1)
ignoreList[i] = ignoreList[i] + ":";
ctx.ignoreList.put(ignoreList[i], ignoreList[i]);
}
}
return dump(o, ctx);
}
protected static String dump(Object o, DumpContext ctx) {
if (o == null) {
return "<null>";
}
ctx.callCount++;
StringBuffer tabs = new StringBuffer();
for (int k = 0; k < ctx.callCount; k++) {
tabs.append("\t");
}
StringBuffer buffer = new StringBuffer();
@SuppressWarnings("rawtypes")
Class oClass = o.getClass();
String oSimpleName = getSimpleNameWithoutArrayQualifier(oClass);
if (ctx.ignoreList.get(oSimpleName + ":") != null)
return "<Ignored>";
if (oClass.isArray()) {
buffer.append("\n");
buffer.append(tabs.toString().substring(1));
buffer.append("[\n");
int rowCount = ctx.maxArrayElements == 0 ? Array.getLength(o) : Math.min(ctx.maxArrayElements, Array.getLength(o));
for (int i = 0; i < rowCount; i++) {
buffer.append(tabs.toString());
try {
Object value = Array.get(o, i);
buffer.append(dumpValue(value, ctx));
} catch (Exception e) {
buffer.append(e.getMessage());
}
if (i < Array.getLength(o) - 1)
buffer.append(",");
buffer.append("\n");
}
if (rowCount < Array.getLength(o)) {
buffer.append(tabs.toString());
buffer.append(Array.getLength(o) - rowCount + " more array elements...");
buffer.append("\n");
}
buffer.append(tabs.toString().substring(1));
buffer.append("]");
} else {
buffer.append("\n");
buffer.append(tabs.toString().substring(1));
buffer.append("{\n");
buffer.append(tabs.toString());
buffer.append("hashCode: " + o.hashCode());
buffer.append("\n");
while (oClass != null && oClass != Object.class) {
Field[] fields = oClass.getDeclaredFields();
if (ctx.ignoreList.get(oClass.getSimpleName()) == null) {
if (oClass != o.getClass()) {
buffer.append(tabs.toString().substring(1));
buffer.append(" Inherited from superclass " + oSimpleName + ":\n");
}
for (int i = 0; i < fields.length; i++) {
String fSimpleName = getSimpleNameWithoutArrayQualifier(fields[i].getType());
String fName = fields[i].getName();
fields[i].setAccessible(true);
buffer.append(tabs.toString());
buffer.append(fName + "(" + fSimpleName + ")");
buffer.append("=");
if (ctx.ignoreList.get(":" + fName) == null &&
ctx.ignoreList.get(fSimpleName + ":" + fName) == null &&
ctx.ignoreList.get(fSimpleName + ":") == null) {
try {
Object value = fields[i].get(o);
buffer.append(dumpValue(value, ctx));
} catch (Exception e) {
buffer.append(e.getMessage());
}
buffer.append("\n");
} else {
buffer.append("<Ignored>");
buffer.append("\n");
}
}
oClass = oClass.getSuperclass();
oSimpleName = oClass.getSimpleName();
} else {
oClass = null;
oSimpleName = "";
}
}
buffer.append(tabs.toString().substring(1));
buffer.append("}");
}
ctx.callCount--;
return buffer.toString();
}
protected static String dumpValue(Object value, DumpContext ctx) {
if (value == null) {
return "<null>";
}
if (value.getClass().isPrimitive() ||
value.getClass() == java.lang.Short.class ||
value.getClass() == java.lang.Long.class ||
value.getClass() == java.lang.String.class ||
value.getClass() == java.lang.Integer.class ||
value.getClass() == java.lang.Float.class ||
value.getClass() == java.lang.Byte.class ||
value.getClass() == java.lang.Character.class ||
value.getClass() == java.lang.Double.class ||
value.getClass() == java.lang.Boolean.class) {
return value.toString();
} else {
Integer visitedIndex = ctx.visited.get(value);
if (visitedIndex == null) {
ctx.visited.put(value, ctx.callCount);
if (ctx.maxDepth == 0 || ctx.callCount < ctx.maxDepth) {
return dump(value, ctx);
} else {
return "<Reached max recursion depth>";
}
} else {
return "<Previously visited - see hashCode " + value.hashCode() + ">";
}
}
}
private static String getSimpleNameWithoutArrayQualifier(@SuppressWarnings("rawtypes") Class clazz) {
String simpleName = clazz.getSimpleName();
int indexOfBracket = simpleName.indexOf('[');
if (indexOfBracket != -1)
return simpleName.substring(0, indexOfBracket);
return simpleName;
}
}

View File

@ -0,0 +1,65 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
public class GoNoGoLights {
private Boolean state;
private Boolean missing;
private Boolean set;
private ImageView red;
private ImageView green;
private Drawable dRed;
private Drawable dGreen;
private Drawable dGray;
public GoNoGoLights(ImageView in_red, ImageView in_green, Resources r) {
red = in_red;
green = in_green;
state = false;
missing = true;
set = false;
dRed = in_red.getContext().getDrawable(R.drawable.redled);
dGreen = in_red.getContext().getDrawable(R.drawable.greenled);
dGray = in_red.getContext().getDrawable(R.drawable.grayled);
}
public void set(Boolean s, Boolean m) {
if (set && s == state && m == missing) return;
state = s;
missing = m;
set = true;
if (missing) {
red.setImageDrawable(dGray);
green.setImageDrawable(dGray);
} else if (state) {
red.setImageDrawable(dGray);
green.setImageDrawable(dGreen);
} else {
red.setImageDrawable(dRed);
green.setImageDrawable(dGray);
}
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright © 2016 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.*;
import org.altusmetrum.altoslib_14.*;
public class IdleModeActivity extends Activity {
private EditText callsignText;
private TextView frequencyView;
private Button connect;
private Button disconnect;
private Button reboot;
private Button igniters;
private double frequency;
public static final String EXTRA_IDLE_MODE = "idle_mode";
public static final String EXTRA_IDLE_RESULT = "idle_result";
public static final int IDLE_MODE_CONNECT = 1;
public static final int IDLE_MODE_REBOOT = 2;
public static final int IDLE_MODE_IGNITERS = 3;
public static final int IDLE_MODE_DISCONNECT = 4;
private void done(int type) {
AltosPreferences.set_callsign(callsign());
Intent intent = new Intent();
intent.putExtra(EXTRA_IDLE_RESULT, type);
setResult(Activity.RESULT_OK, intent);
finish();
}
private String callsign() {
return callsignText.getEditableText().toString();
}
public void connect_idle() {
done(IDLE_MODE_CONNECT);
}
public void disconnect_idle() {
AltosDebug.debug("Disconnect idle button pressed");
done(IDLE_MODE_DISCONNECT);
}
public void reboot_idle() {
done(IDLE_MODE_REBOOT);
}
public void igniters_idle() {
done(IDLE_MODE_IGNITERS);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
// Setup the window
setContentView(R.layout.idle_mode);
callsignText = (EditText) findViewById(R.id.set_callsign);
callsignText.setText(new StringBuffer(AltosPreferences.callsign()));
frequency = getIntent().getDoubleExtra(AltosDroid.EXTRA_FREQUENCY, 0.0);
frequencyView = (TextView) findViewById(R.id.frequency);
frequencyView.setText(String.format("Frequency: %7.3f MHz", frequency));
connect = (Button) findViewById(R.id.connect_idle);
connect.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
connect_idle();
}
});
disconnect = (Button) findViewById(R.id.disconnect_idle);
disconnect.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
disconnect_idle();
}
});
boolean idle_mode = getIntent().getBooleanExtra(AltosDroid.EXTRA_IDLE_MODE, false);
if (idle_mode)
connect.setVisibility(View.GONE);
else
disconnect.setVisibility(View.GONE);
reboot = (Button) findViewById(R.id.reboot_idle);
reboot.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
reboot_idle();
}
});
igniters = (Button) findViewById(R.id.igniters_idle);
igniters.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
igniters_idle();
}
});
// Set result CANCELED incase the user backs out
setResult(Activity.RESULT_CANCELED);
}
}

View File

@ -0,0 +1,411 @@
/*
* Copyright © 2016 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.lang.ref.WeakReference;
import java.util.*;
import android.app.Activity;
import android.content.*;
import android.graphics.*;
import android.os.*;
import android.view.*;
import android.view.View.*;
import android.widget.*;
import org.altusmetrum.altoslib_14.*;
class IgniterItem {
public String name;
public String pretty;
public String status;
public LinearLayout igniter_view = null;
public TextView pretty_view = null;
public TextView status_view = null;
private void update() {
if (pretty_view != null)
pretty_view.setText(pretty);
if (status_view != null)
status_view.setText(status);
}
public void set(String name, String pretty, String status) {
if (!name.equals(this.name) ||
!pretty.equals(this.pretty) ||
!status.equals(this.status))
{
this.name = name;
this.pretty = pretty;
this.status = status;
update();
}
}
public void realize(LinearLayout igniter_view,
TextView pretty_view,
TextView status_view) {
if (igniter_view != this.igniter_view ||
pretty_view != this.pretty_view ||
status_view != this.status_view)
{
this.igniter_view = igniter_view;
this.pretty_view = pretty_view;
this.status_view = status_view;
update();
}
}
public IgniterItem() {
}
}
class IgniterAdapter extends ArrayAdapter<IgniterItem> {
int resource;
int selected_item = -1;
public IgniterAdapter(Context context, int in_resource) {
super(context, in_resource);
resource = in_resource;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
IgniterItem item = getItem(position);
if (item.igniter_view == null) {
LinearLayout igniter_view = new LinearLayout(getContext());
String inflater = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater li = (LayoutInflater) getContext().getSystemService(inflater);
li.inflate(resource, igniter_view, true);
item.realize(igniter_view,
(TextView) igniter_view.findViewById(R.id.igniter_name),
(TextView) igniter_view.findViewById(R.id.igniter_status));
}
if (position == selected_item)
item.igniter_view.setBackgroundColor(Color.RED);
else
item.igniter_view.setBackgroundColor(0);
return item.igniter_view;
}
}
public class IgniterActivity extends Activity {
private ListView igniters_view;
private ToggleButton arm;
private Button fire;
private HashMap<String,IgniterItem> igniters = new HashMap<String,IgniterItem>();;
private IgniterAdapter igniters_adapter;
private boolean is_bound;
private Messenger service = null;
private final Messenger messenger = new Messenger(new IncomingHandler(this));
private Timer query_timer;
private boolean query_timer_running;
private Timer arm_timer;
private int arm_remaining;
public static final int IGNITER_QUERY = 1;
public static final int IGNITER_FIRE = 2;
// The Handler that gets information back from the Telemetry Service
static class IncomingHandler extends Handler {
private final WeakReference<IgniterActivity> igniter_activity;
IncomingHandler(IgniterActivity ia) { igniter_activity = new WeakReference<IgniterActivity>(ia); }
@Override
public void handleMessage(Message msg) {
IgniterActivity ia = igniter_activity.get();
switch (msg.what) {
case AltosDroid.MSG_IGNITER_STATUS:
@SuppressWarnings("unchecked") HashMap<String,Integer> map = (HashMap <String,Integer>) msg.obj;
ia.igniter_status(map);
break;
}
}
};
private ServiceConnection connection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) {
service = new Messenger(binder);
query_timer_tick();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been unexpectedly disconnected - process crashed.
service = null;
}
};
void doBindService() {
bindService(new Intent(this, TelemetryService.class), connection, Context.BIND_AUTO_CREATE);
is_bound = true;
}
void doUnbindService() {
if (is_bound) {
// If we have received the service, and hence registered with it, then now is the time to unregister.
unbindService(connection);
is_bound = false;
}
}
private void done() {
Intent intent = new Intent();
setResult(Activity.RESULT_OK, intent);
finish();
}
class FireThread extends Thread {
private final String igniter;
@Override
public void run() {
Message msg = Message.obtain(null, TelemetryService.MSG_IGNITER_FIRE, igniter);
try {
service.send(msg);
} catch (RemoteException re) {
}
}
public FireThread(String igniter) {
this.igniter = igniter;
}
}
private void fire_igniter() {
if (igniters_adapter.selected_item >= 0) {
IgniterItem item = igniters_adapter.getItem(igniters_adapter.selected_item);
FireThread ft = new FireThread(item.name);
ft.run();
arm.setChecked(false);
}
}
private void arm_igniter(boolean is_checked) {
if (is_checked) {
arm_timer_stop();
arm_timer = new Timer();
arm_remaining = 10;
arm_set_text();
fire.setEnabled(true);
arm_timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
arm_timer_tick();
}},
1000L, 1000L);
} else {
arm_timer_stop();
fire.setEnabled(false);
}
}
private synchronized void query_timer_tick() {
if (query_timer_running)
return;
if (service == null)
return;
query_timer_running = true;
Thread thread = new Thread(new Runnable() {
public void run() {
try {
Message msg = Message.obtain(null, TelemetryService.MSG_IGNITER_QUERY);
msg.replyTo = messenger;
if (service == null) {
synchronized(IgniterActivity.this) {
query_timer_running = false;
}
} else
service.send(msg);
} catch (RemoteException re) {
AltosDebug.debug("igniter query thread failed");
synchronized(IgniterActivity.this) {
query_timer_running = false;
}
}
}
});
thread.start();
}
private boolean set_igniter(HashMap <String,Integer> status, String name, String pretty) {
if (!status.containsKey(name))
return false;
IgniterItem item;
if (!igniters.containsKey(name)) {
item = new IgniterItem();
igniters.put(name, item);
igniters_adapter.add(item);
} else
item = igniters.get(name);
item.set(name, pretty, AltosIgnite.status_string(status.get(name)));
return true;
}
private synchronized void igniter_status(HashMap <String,Integer> status) {
query_timer_running = false;
if (status == null) {
AltosDebug.debug("no igniter status");
return;
}
set_igniter(status, "drogue", "Apogee");
set_igniter(status, "main", "Main");
for (int extra = 0;; extra++) {
String name = String.format("%d", extra);
String pretty = String.format("%c", 'A' + extra);
if (!set_igniter(status, name, pretty))
break;
}
}
private synchronized void arm_timer_stop() {
if (arm_timer != null) {
arm_timer.cancel();
arm_timer = null;
}
arm_remaining = 0;
}
private void arm_set_text() {
String text = String.format("Armed %d", arm_remaining);
if (arm.isChecked())
arm.setText(text);
arm.setTextOn(text);
}
private void arm_timer_tick() {
--arm_remaining;
if (arm_remaining <= 0) {
arm_timer_stop();
runOnUiThread(new Runnable() {
public void run() {
arm.setChecked(false);
fire.setEnabled(false);
}
});
} else {
runOnUiThread(new Runnable() {
public void run() {
arm_set_text();
}
});
}
}
private void select_item(int position) {
if (position != igniters_adapter.selected_item) {
if (igniters_adapter.selected_item >= 0)
igniters_view.setItemChecked(igniters_adapter.selected_item, false);
if (position >= 0) {
igniters_view.setItemChecked(position, true);
arm.setEnabled(true);
} else
arm.setEnabled(false);
igniters_adapter.selected_item = position;
}
}
private class IgniterItemClickListener implements ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> av, View v, int position, long id) {
AltosDebug.debug("select %d\n", position);
select_item(position);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
// Setup the window
setContentView(R.layout.igniters);
igniters_view = (ListView) findViewById(R.id.igniters);
igniters_view.setClickable(true);
igniters_adapter = new IgniterAdapter(this, R.layout.igniter_status);
igniters_view.setAdapter(igniters_adapter);
igniters_view.setOnItemClickListener(new IgniterItemClickListener());
fire = (Button) findViewById(R.id.igniter_fire);
fire.setEnabled(false);
fire.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
fire_igniter();
}
});
arm = (ToggleButton) findViewById(R.id.igniter_arm);
arm.setEnabled(false);
arm.setOnCheckedChangeListener(new ToggleButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton v, boolean is_checked) {
arm_igniter(is_checked);
}
});
// Set result CANCELED incase the user backs out
setResult(Activity.RESULT_CANCELED);
}
@Override
protected void onStart() {
super.onStart();
doBindService();
}
@Override
protected void onResume() {
super.onResume();
query_timer = new Timer(true);
query_timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
query_timer_tick();
}},
0L, 5000L);
}
@Override
protected void onPause() {
super.onPause();
if (query_timer != null) {
query_timer.cancel();
query_timer = null;
}
arm_timer_stop();
arm.setChecked(false);
fire.setEnabled(false);
}
@Override
protected void onStop() {
super.onStop();
doUnbindService();
}
}

View File

@ -0,0 +1,304 @@
/*
* Copyright © 2016 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.util.*;
import java.text.*;
import android.app.Activity;
import android.content.*;
import android.graphics.*;
import android.os.*;
import android.view.*;
import android.view.View.*;
import android.view.inputmethod.*;
import android.widget.*;
import org.altusmetrum.altoslib_14.*;
class FrequencyItem {
public AltosFrequency frequency;
public LinearLayout frequency_view = null;
public TextView pretty_view = null;
private void update() {
if (pretty_view != null && frequency != null)
pretty_view.setText(frequency.toString());
}
public void realize(LinearLayout frequency_view,
TextView pretty_view) {
if (frequency_view != this.frequency_view ||
pretty_view != this.pretty_view)
{
this.frequency_view = frequency_view;
this.pretty_view = pretty_view;
update();
}
}
public void set_frequency(AltosFrequency frequency) {
this.frequency = frequency;
update();
}
public FrequencyItem(AltosFrequency frequency) {
this.frequency = frequency;
}
}
class FrequencyAdapter extends ArrayAdapter<FrequencyItem> {
int resource;
int selected_item = -1;
public FrequencyAdapter(Context context, int in_resource) {
super(context, in_resource);
resource = in_resource;
}
public int count() {
int count;
for (count = 0;; count++) {
try {
getItem(count);
} catch (IndexOutOfBoundsException ie) {
return count;
}
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
FrequencyItem item = getItem(position);
if (item.frequency_view == null) {
LinearLayout frequency_view = new LinearLayout(getContext());
String inflater = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater li = (LayoutInflater) getContext().getSystemService(inflater);
li.inflate(resource, frequency_view, true);
item.realize(frequency_view,
(TextView) frequency_view.findViewById(R.id.frequency));
}
if (position == selected_item)
item.frequency_view.setBackgroundColor(Color.RED);
else
item.frequency_view.setBackgroundColor(0);
return item.frequency_view;
}
}
public class ManageFrequenciesActivity extends Activity {
private ListView frequencies_view;
private Button set;
private Button remove;
private Button done;
private EditText set_frequency;
private EditText set_description;
private HashMap<String,FrequencyItem> frequencies = new HashMap<String,FrequencyItem>();;
private FrequencyAdapter frequencies_adapter;
private boolean is_bound;
private boolean changed = false;
private void done() {
set();
if (changed) {
AltosFrequency[] frequencies = new AltosFrequency[frequencies_adapter.count()];
for (int i = 0; i < frequencies.length; i++)
frequencies[i] = frequencies_adapter.getItem(i).frequency;
AltosPreferences.set_common_frequencies(frequencies);
}
Intent intent = new Intent();
setResult(Activity.RESULT_OK, intent);
finish();
}
private void load_item() {
if (frequencies_adapter.selected_item >= 0) {
FrequencyItem item = frequencies_adapter.getItem(frequencies_adapter.selected_item);
set_frequency.setText(item.frequency.frequency_string());
set_description.setText(item.frequency.description);
} else {
set_frequency.setText("");
set_description.setText("");
}
}
private void select_item(int position) {
if (position != frequencies_adapter.selected_item) {
if (frequencies_adapter.selected_item >= 0)
frequencies_view.setItemChecked(frequencies_adapter.selected_item, false);
if (position >= 0)
frequencies_view.setItemChecked(position, true);
frequencies_adapter.selected_item = position;
} else {
if (frequencies_adapter.selected_item >= 0)
frequencies_view.setItemChecked(frequencies_adapter.selected_item, false);
frequencies_adapter.selected_item = -1;
}
load_item();
}
private int find(AltosFrequency frequency) {
for (int pos = 0; pos < frequencies_adapter.getCount(); pos++) {
FrequencyItem item = frequencies_adapter.getItem(pos);
if (item.frequency.frequency == frequency.frequency &&
item.frequency.description.equals(frequency.description))
return pos;
}
return -1;
}
private int insert_item(AltosFrequency frequency) {
FrequencyItem new_item = new FrequencyItem(frequency);
int pos;
for (pos = 0; pos < frequencies_adapter.getCount(); pos++) {
FrequencyItem item = frequencies_adapter.getItem(pos);
if (item.frequency.frequency == new_item.frequency.frequency) {
item.set_frequency(frequency);
return pos;
}
if (item.frequency.frequency > new_item.frequency.frequency)
break;
}
frequencies_adapter.insert(new_item, pos);
return pos;
}
private class FrequencyItemClickListener implements ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> av, View v, int position, long id) {
select_item(position);
}
}
private void hide_keyboard() {
InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
View view = getCurrentFocus();
if (view != null)
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void set() {
String frequency_text = set_frequency.getEditableText().toString();
String description_text = set_description.getEditableText().toString();
try {
double f = AltosParse.parse_double_locale(frequency_text);
AltosFrequency frequency = new AltosFrequency(f, description_text);
int pos;
pos = find(frequency);
if (pos < 0) {
pos = insert_item(frequency);
changed = true;
}
frequencies_adapter.selected_item = -1;
select_item(pos);
} catch (ParseException pe) {
}
hide_keyboard();
}
private void remove() {
if (frequencies_adapter.selected_item >= 0) {
frequencies_adapter.remove(frequencies_adapter.getItem(frequencies_adapter.selected_item));
select_item(-1);
frequencies_view.setAdapter(frequencies_adapter);
changed = true;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
// Setup the window
setContentView(R.layout.manage_frequencies);
frequencies_view = (ListView) findViewById(R.id.frequencies);
frequencies_view.setClickable(true);
frequencies_adapter = new FrequencyAdapter(this, R.layout.frequency);
frequencies_view.setAdapter(frequencies_adapter);
frequencies_view.setOnItemClickListener(new FrequencyItemClickListener());
AltosFrequency[] frequencies = AltosPreferences.common_frequencies();
for (AltosFrequency frequency : frequencies)
insert_item(frequency);
set_frequency = (EditText) findViewById(R.id.set_frequency);
set_description = (EditText) findViewById(R.id.set_description);
set = (Button) findViewById(R.id.set);
set.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
set();
}
});
remove = (Button) findViewById(R.id.remove);
remove.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
remove();
}
});
done = (Button) findViewById(R.id.done);
done.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
done();
}
});
// Set result CANCELED incase the user backs out
setResult(Activity.RESULT_CANCELED);
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
}
}

View File

@ -0,0 +1,75 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.*;
import org.altusmetrum.altoslib_14.*;
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) {
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
// Setup the window
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);
}
}

View File

@ -0,0 +1,378 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.util.*;
import java.text.*;
import android.app.Activity;
import android.content.Context;
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 org.altusmetrum.altoslib_14.*;
/**
* 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, 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;
private AltosMapLoader loader;
long loader_notify_time;
/* AltosMapLoaderListener interfaces */
public void loader_start(final int max) {
loader_notify_time = System.currentTimeMillis();
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) {
long now = System.currentTimeMillis();
if (now - loader_notify_time < 100)
return;
loader_notify_time = now;
this.runOnUiThread(new Runnable() {
public void run() {
progress.setProgress(cur);
}
});
}
public void loader_done(int max) {
loader = null;
this.runOnUiThread(new Runnable() {
public void run() {
progress.setProgress(0);
finish();
}
});
}
public void debug(String format, Object ... arguments) {
AltosDebug.debug(format, arguments);
}
/* 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);
}
});
}
/* 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.miles_to_meters(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));
*/
return 1 << AltosMap.maptype_hybrid;
}
private void load() {
if (loader != null)
return;
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 = new AltosMapLoader(this, lat, lon, min, max, r, t, AltosMapOffline.scale);
} 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) {
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
// Setup the window
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());
// Listen for GPS and Network position updates
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
try {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this);
} catch (Exception e) {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 1, this);
}
new AltosLaunchSites(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (loader != null)
loader.abort();
// Stop listening for location updates
((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this);
}
}

View File

@ -0,0 +1,297 @@
/*
* Copyright © 2020 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, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.util.*;
import android.app.Activity;
import android.content.*;
import android.os.*;
import android.util.*;
import android.view.*;
import android.view.View.*;
import android.widget.*;
import android.graphics.*;
import android.graphics.drawable.*;
import android.widget.CompoundButton.OnCheckedChangeListener;
import org.altusmetrum.altoslib_14.*;
class TrackerComparatorCall implements Comparator<Tracker> {
public int compare(Tracker a, Tracker b) {
int v;
v = a.compareCall(b);
if (v != 0)
return v;
v = a.compareAge(b);
if (v != 0)
return v;
v = a.compareSerial(b);
if (v != 0)
return v;
return a.compareFrequency(b);
}
public boolean equals(Object o) {
return o instanceof TrackerComparatorCall;
}
}
class TrackerComparatorSerial implements Comparator<Tracker> {
public int compare(Tracker a, Tracker b) {
int v;
v = a.compareSerial(b);
if (v != 0)
return v;
v = a.compareAge(b);
if (v != 0)
return v;
v = a.compareCall(b);
if (v != 0)
return v;
return a.compareFrequency(b);
}
public boolean equals(Object o) {
return o instanceof TrackerComparatorSerial;
}
}
class TrackerComparatorAge implements Comparator<Tracker> {
public int compare(Tracker a, Tracker b) {
int v;
v = a.compareAge(b);
if (v != 0)
return v;
v = a.compareCall(b);
if (v != 0)
return v;
v = a.compareSerial(b);
if (v != 0)
return v;
return a.compareFrequency(b);
}
public boolean equals(Object o) {
return o instanceof TrackerComparatorAge;
}
}
class TrackerComparatorFrequency implements Comparator<Tracker> {
public int compare(Tracker a, Tracker b) {
int v;
v = a.compareFrequency(b);
if (v != 0)
return v;
v = a.compareAge(b);
if (v != 0)
return v;
v = a.compareCall(b);
if (v != 0)
return v;
return a.compareSerial(b);
}
public boolean equals(Object o) {
return o instanceof TrackerComparatorFrequency;
}
}
public class SelectTrackerActivity extends Activity implements OnTouchListener {
// Return Intent extra
public static final String EXTRA_SERIAL_NUMBER = "serial_number";
public static final String EXTRA_FREQUENCY = "frequency";
private int button_ids[] = {
R.id.call_button,
R.id.serial_button,
R.id.frequency_button,
R.id.age_button
};
private static final int call_button = 0;
private static final int serial_button = 1;
private static final int freq_button = 2;
private static final int age_button = 3;
private RadioButton radio_buttons[] = new RadioButton[4];
private TableLayout table;
private Tracker[] trackers;
private void set_sort(int id) {
AltosDroidPreferences.set_tracker_sort(id);
resort();
}
private void resort() {
Comparator<Tracker> compare;
int tracker_sort = AltosDroidPreferences.tracker_sort();
AltosDebug.debug("sort %d", tracker_sort);
switch (tracker_sort) {
case call_button:
default:
compare = new TrackerComparatorCall();
break;
case serial_button:
compare = new TrackerComparatorSerial();
break;
case freq_button:
compare = new TrackerComparatorFrequency();
break;
case age_button:
compare = new TrackerComparatorAge();
break;
}
Arrays.sort(trackers, compare);
set_trackers();
}
void init_button_state() {
int tracker_sort = AltosDroidPreferences.tracker_sort();
for (int i = 0; i < 4; i++)
radio_buttons[i].setChecked(i == tracker_sort);
}
OnCheckedChangeListener button_listener = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int id = buttonView.getId();
if (isChecked) {
int sort_id = -1;
for (int i = 0; i < 4; i++) {
if (id == button_ids[i])
sort_id = i;
else
radio_buttons[i].setChecked(false);
}
if (sort_id != -1)
set_sort(sort_id);
}
}
};
long start_time;
private void
insert_tracker(Tracker tracker) {
TableRow row = (TableRow) getLayoutInflater().inflate(R.layout.tracker_ent, null);
((TextView) row.findViewById(R.id.call_view)).setText(tracker.call);
if (tracker.serial == 0)
((TextView) row.findViewById(R.id.serial_view)).setText("");
else
((TextView) row.findViewById(R.id.serial_view)).setText(String.format("%d", tracker.serial));
if (tracker.frequency == 0.0)
((TextView) row.findViewById(R.id.frequency_view)).setText("");
else if (tracker.frequency == AltosLib.MISSING)
((TextView) row.findViewById(R.id.frequency_view)).setText("");
else
((TextView) row.findViewById(R.id.frequency_view)).setText(String.format("%7.3f", tracker.frequency));
if (tracker.received_time != 0) {
int age = (int) ((start_time - tracker.received_time + 500) / 1000);
((TextView) row.findViewById(R.id.age_view)).setText(AltosDroid.age_string(age));
} else {
((TextView) row.findViewById(R.id.age_view)).setText("");
}
row.setClickable(true);
row.setOnTouchListener(this);
table.addView(row);
}
private void set_trackers() {
for (int i = table.getChildCount() - 1; i >= 1; i--)
table.removeViewAt(i);
for (Tracker tracker : trackers)
insert_tracker(tracker);
}
private void done(View v) {
int result = Activity.RESULT_CANCELED;
Intent intent = new Intent();
for (int i = 1; i < table.getChildCount(); i++) {
View child = table.getChildAt(i);
if (child == v) {
Tracker tracker = trackers[i - 1];
intent.putExtra(EXTRA_SERIAL_NUMBER, tracker.serial);
intent.putExtra(EXTRA_FREQUENCY, tracker.frequency);
result = Activity.RESULT_OK;
break;
}
}
setResult(Activity.RESULT_OK, intent);
finish();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
int title_id = getIntent().getIntExtra(AltosDroid.EXTRA_TRACKERS_TITLE, R.id.select_tracker);
AltosDebug.debug("get title id 0x%x %s", title_id, getResources().getText(title_id));
setTitle(getResources().getText(title_id));
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
setContentView(R.layout.tracker_list);
// Set result CANCELED incase the user backs out
setResult(Activity.RESULT_CANCELED);
for (int i = 0; i < 4; i++) {
radio_buttons[i] = (RadioButton) findViewById(button_ids[i]);
radio_buttons[i].setOnCheckedChangeListener(button_listener);
}
ArrayList<Parcelable> tracker_array = (ArrayList<Parcelable>) getIntent().getParcelableArrayListExtra(AltosDroid.EXTRA_TRACKERS);
if (tracker_array != null) {
Object[] array = tracker_array.toArray();
trackers = new Tracker[array.length];
for (int i = 0; i < array.length; i++)
trackers[i] = (Tracker) array[i];
}
start_time = System.currentTimeMillis();
table = (TableLayout) findViewById(R.id.tracker_list);
init_button_state();
resort();
set_trackers();
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_OUTSIDE:
v.setBackgroundColor(0);
v.invalidate();
break;
case MotionEvent.ACTION_DOWN:
v.setBackgroundColor(Color.RED);
v.invalidate();
break;
}
if (action == MotionEvent.ACTION_UP) {
done(v);
return true;
}
return false;
}
}

View File

@ -0,0 +1,365 @@
/*
* Copyright © 2016 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import android.app.Activity;
import android.content.*;
import android.os.*;
import android.view.*;
import android.view.View.*;
import android.widget.*;
import android.widget.AdapterView.*;
import org.altusmetrum.altoslib_14.*;
public class SetupActivity extends Activity {
private Spinner select_rate;
private Spinner set_units;
private Spinner font_size;
private Spinner map_type;
private Spinner map_source;
private Button manage_frequencies;
private Button preload_maps;
private Button done;
private boolean is_bound;
private Messenger service = null;
public final static String EXTRA_SETUP_CHANGES = "setup_changes";
private ServiceConnection connection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) {
service = new Messenger(binder);
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been unexpectedly disconnected - process crashed.
service = null;
}
};
void doBindService() {
bindService(new Intent(this, TelemetryService.class), connection, Context.BIND_AUTO_CREATE);
is_bound = true;
}
void doUnbindService() {
if (is_bound) {
// If we have received the service, and hence registered with it, then now is the time to unregister.
unbindService(connection);
is_bound = false;
}
}
static final String[] rates = {
"38400",
"9600",
"2400",
};
static final String[] sizes = {
"Small",
"Medium",
"Large",
"Extra"
};
static final String[] map_types = {
"Hybrid",
"Satellite",
"Roadmap",
"Terrain"
};
static final int[] map_type_values = {
AltosMap.maptype_hybrid,
AltosMap.maptype_satellite,
AltosMap.maptype_roadmap,
AltosMap.maptype_terrain,
};
static final String[] map_sources = {
"Online",
"Offline"
};
private int set_telemetry_rate;
private int set_map_source;
private int set_map_type;
private boolean set_imperial_units;
private int set_font_size;
private void done() {
int changes = 0;
Intent intent = new Intent();
if (set_telemetry_rate != AltosPreferences.telemetry_rate(1)) {
changes |= AltosDroid.SETUP_BAUD;
AltosPreferences.set_telemetry_rate(1, set_telemetry_rate);
}
if (set_imperial_units != AltosPreferences.imperial_units()) {
changes |= AltosDroid.SETUP_UNITS;
AltosPreferences.set_imperial_units(set_imperial_units);
}
if (set_map_source != AltosDroidPreferences.map_source()) {
changes |= AltosDroid.SETUP_MAP_SOURCE;
AltosDroidPreferences.set_map_source(set_map_source);
}
if (set_map_type != AltosPreferences.map_type()) {
changes |= AltosDroid.SETUP_MAP_TYPE;
AltosPreferences.set_map_type(set_map_type);
}
if (set_font_size != AltosDroidPreferences.font_size()) {
changes |= AltosDroid.SETUP_FONT_SIZE;
AltosDroidPreferences.set_font_size(set_font_size);
}
intent.putExtra(EXTRA_SETUP_CHANGES, changes);
setResult(Activity.RESULT_OK, intent);
finish();
}
private void add_strings(Spinner spinner, String[] strings, int def) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item);
for (int i = 0; i < strings.length; i++)
adapter.add(strings[i]);
spinner.setAdapter(adapter);
if (def >= 0)
spinner.setSelection(def);
}
private int default_rate_pos() {
int default_rate = AltosPreferences.telemetry_rate(1);
for (int pos = 0; pos < rates.length; pos++) {
if (string_to_rate(rates[pos]) == default_rate)
return pos;
}
return -1;
}
private void setBaud(int baud) {
set_telemetry_rate = baud;
}
private int string_to_rate(String baud) {
int rate = AltosLib.ao_telemetry_rate_38400;
try {
int value = Integer.parseInt(baud);
switch (value) {
case 2400:
rate = AltosLib.ao_telemetry_rate_2400;
break;
case 9600:
rate = AltosLib.ao_telemetry_rate_9600;
break;
case 38400:
rate = AltosLib.ao_telemetry_rate_38400;
break;
}
} catch (NumberFormatException e) {
}
return rate;
}
private void setBaud(String baud) {
setBaud(string_to_rate(baud));
}
private void select_rate(int pos) {
setBaud(rates[pos]);
}
static final String[] units = {
"Metric",
"Imperial"
};
private int default_units_pos() {
boolean imperial = AltosPreferences.imperial_units();
if (imperial)
return 1;
return 0;
}
private void set_units(int pos) {
switch (pos) {
default:
set_imperial_units = false;
break;
case 1:
set_imperial_units = true;
break;
}
}
private void set_font_size(int pos) {
set_font_size = pos;
}
private int default_map_type_pos() {
int default_map_type = AltosPreferences.map_type();
for (int pos = 0; pos < map_types.length; pos++)
if (map_type_values[pos] == default_map_type)
return pos;
return 0;
}
private void select_map_type(int pos) {
set_map_type = map_type_values[pos];
}
private int default_map_source_pos() {
int default_source = AltosDroidPreferences.map_source();
switch (default_source) {
case AltosDroidPreferences.MAP_SOURCE_OFFLINE:
return 1;
default:
return 0;
}
}
private void select_map_source(int pos) {
switch (pos) {
default:
set_map_source = AltosDroidPreferences.MAP_SOURCE_ONLINE;
break;
case 1:
set_map_source = AltosDroidPreferences.MAP_SOURCE_OFFLINE;
break;
}
}
private void manage_frequencies(){
Intent intent = new Intent(this, ManageFrequenciesActivity.class);
startActivity(intent);
}
private void preload_maps(){
Intent intent = new Intent(this, PreloadMapActivity.class);
startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
AltosDebug.init(this);
AltosDebug.debug("+++ ON CREATE +++");
// Initialise preferences
AltosDroidPreferences.init(this);
// Setup the window
setContentView(R.layout.setup);
select_rate = (Spinner) findViewById(R.id.select_rate);
add_strings(select_rate, rates, default_rate_pos());
select_rate.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
AltosDebug.debug("rate selected pos %d id %d", pos, id);
select_rate(pos);
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
set_units = (Spinner) findViewById(R.id.set_units);
add_strings(set_units, units, default_units_pos());
set_units.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
set_units(pos);
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
font_size = (Spinner) findViewById(R.id.font_size);
add_strings(font_size, sizes, AltosDroidPreferences.font_size());
font_size.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
set_font_size(pos);
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
map_type = (Spinner) findViewById(R.id.map_type);
add_strings(map_type, map_types, default_map_type_pos());
map_type.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
select_map_type(pos);
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
map_source = (Spinner) findViewById(R.id.map_source);
add_strings(map_source, map_sources, default_map_source_pos());
map_source.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
select_map_source(pos);
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
manage_frequencies = (Button) findViewById(R.id.manage_frequencies);
manage_frequencies.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
manage_frequencies();
}
});
preload_maps = (Button) findViewById(R.id.preload_maps);
preload_maps.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
preload_maps();
}
});
done = (Button) findViewById(R.id.done);
done.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
done();
}
});
// Set result for when the user backs out
setResult(Activity.RESULT_CANCELED);
}
@Override
protected void onStart() {
super.onStart();
doBindService();
}
@Override
protected void onStop() {
super.onStop();
doUnbindService();
}
}

View File

@ -0,0 +1,143 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import org.altusmetrum.altoslib_14.*;
import android.os.Bundle;
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 altitude_view;
private View tilt_view;
private TextView tilt_value;
private TextView max_speed_view;
private TextView max_height_view;
private TextView max_altitude_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);
altitude_view = (TextView) v.findViewById(R.id.altitude_value);
tilt_view = (View) v.findViewById(R.id.tilt_view);
tilt_value = (TextView) v.findViewById(R.id.tilt_value);
max_speed_view = (TextView) v.findViewById(R.id.max_speed_value);
max_height_view = (TextView) v.findViewById(R.id.max_height_value);
max_altitude_view= (TextView) v.findViewById(R.id.max_altitude_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, 1, state.speed());
set_value(height_view, AltosConvert.height, 1, state.height());
set_value(altitude_view, AltosConvert.height, 1, state.altitude());
double orient = state.orient();
if (orient == AltosLib.MISSING) {
tilt_view.setVisibility(View.GONE);
} else {
tilt_value.setText(AltosDroid.number("%1.0f°", orient));
tilt_view.setVisibility(View.VISIBLE);
}
set_value(max_speed_view, AltosConvert.speed, 1, state.max_speed());
set_value(max_height_view, AltosConvert.height, 1, state.max_height());
set_value(max_altitude_view, AltosConvert.height, 1, state.max_altitude());
if (from_receiver != null) {
elevation_view.setText(AltosDroid.number("%1.0f°", from_receiver.elevation));
set_value(range_view, AltosConvert.distance, 1, from_receiver.range);
bearing_view.setText(AltosDroid.number("%1.0f°", from_receiver.bearing));
compass_view.setText(from_receiver.bearing_words(AltosGreatCircle.BEARING_LONG));
set_value(distance_view, AltosConvert.distance, 1, 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("%1.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("%1.2f V", state.main_voltage));
main_lights.set(state.main_voltage > 3.2, state.main_voltage == AltosLib.MISSING);
main_view.setVisibility(View.VISIBLE);
}
}
}
}

View File

@ -0,0 +1,156 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import org.altusmetrum.altoslib_14.*;
import android.app.Activity;
import android.os.Bundle;
import android.view.*;
import android.widget.*;
import android.location.Location;
public class TabMap extends AltosDroidTab implements AltosDroidMapSourceListener {
AltosLatLon here;
private TextView mDistanceView;
private TextView mBearingLabel;
private TextView mBearingView;
private TextView mTargetLatitudeView;
private TextView mTargetLongitudeView;
private TextView mReceiverLatitudeView;
private TextView mReceiverLongitudeView;
private AltosMapOffline map_offline;
private AltosMapOnline map_online;
private View view;
private int map_source;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
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);
map_source_changed(AltosDroidPreferences.map_source());
AltosDroidPreferences.register_map_source_listener(this);
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (map_online != null)
getChildFragmentManager().beginTransaction().add(R.id.map_online, map_online.mMapFragment).commit();
}
@Override
public void onDestroyView() {
super.onDestroyView();
map_offline.onDestroyView();
map_online.onDestroyView();
AltosDroidPreferences.unregister_map_source_listener(this);
}
public String tab_name() { return AltosDroid.tab_map_name; }
private void center(double lat, double lon, double accuracy) {
if (map_offline != null)
map_offline.center(lat, lon, accuracy);
if (map_online != null)
map_online.center(lat, lon, accuracy);
}
public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
if (from_receiver != null) {
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 (state.gps != null) {
mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S"));
mTargetLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W"));
}
}
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(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);
}
}
public void map_source_changed(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() {
}
}

View File

@ -0,0 +1,259 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import org.altusmetrum.altoslib_14.*;
import android.os.Bundle;
import android.view.*;
import android.widget.*;
import android.location.Location;
public class TabPad extends AltosDroidTab {
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];
private View tilt_view;
private TextView tilt_value;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
AltosDebug.debug("TabPad onCreateView\n");
View v = inflater.inflate(R.layout.tab_pad, container, false);
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());
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());
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());
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());
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());
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());
tilt_view = (View) v.findViewById(R.id.tilt_view);
tilt_value = (TextView) v.findViewById(R.id.tilt_value);
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());
}
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);
AltosDebug.debug("TabPad onCreateView done battery_voltage_view %s\n", battery_voltage_view);
return v;
}
public String tab_name() { return AltosDroid.tab_pad_name; }
public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
AltosDebug.debug("pad show state %b bvv %s\n", state != null, battery_voltage_view);
if (state != null) {
battery_voltage_view.setText(AltosDroid.number("%1.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) {
apogee_row.setVisibility(View.GONE);
} else {
apogee_voltage_view.setText(AltosDroid.number("%1.2f V", state.apogee_voltage));
apogee_row.setVisibility(View.VISIBLE);
}
apogee_lights.set(state.apogee_voltage >= AltosLib.ao_igniter_good, state.apogee_voltage == AltosLib.MISSING);
if (state.main_voltage == AltosLib.MISSING) {
main_row.setVisibility(View.GONE);
} else {
main_voltage_view.setText(AltosDroid.number("%1.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.igniter_voltage == null ? 0 : state.igniter_voltage.length;
for (int i = 0; i < 4; i++) {
double voltage = i >= num_igniter ? AltosLib.MISSING : state.igniter_voltage[i];
if (voltage == AltosLib.MISSING) {
ignite_row[i].setVisibility(View.GONE);
} else {
ignite_voltage_view[i].setText(AltosDroid.number("%1.2f V", voltage));
ignite_row[i].setVisibility(View.VISIBLE);
}
ignite_lights[i].set(voltage >= AltosLib.ao_igniter_good, voltage == AltosLib.MISSING);
}
if (state.cal_data().flight != 0) {
if (state.state() <= AltosLib.ao_flight_pad)
data_logging_view.setText("Ready to record");
else if (state.state() < AltosLib.ao_flight_landed)
data_logging_view.setText("Recording data");
else
data_logging_view.setText("Recorded data");
} else {
data_logging_view.setText("Storage full");
}
data_logging_lights.set(state.cal_data().flight != 0, state.cal_data().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;
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)
gps_ready_view.setText("Ready");
else
gps_ready_view.setText(AltosDroid.integer("Waiting %d", state.gps_waiting));
} else
gps_locked_lights.set(false, true);
gps_ready_lights.set(state.gps_ready, state.gps == null);
double orient = state.orient();
if (orient == AltosLib.MISSING) {
tilt_view.setVisibility(View.GONE);
} else {
tilt_value.setText(AltosDroid.number("%1.0f°", orient));
tilt_view.setVisibility(View.VISIBLE);
}
}
if (telem_state != null) {
if (telem_state.receiver_battery == AltosLib.MISSING) {
receiver_row.setVisibility(View.GONE);
} else {
receiver_voltage_view.setText(AltosDroid.number("%1.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();
String lat_text = AltosDroid.pos(receiver.getLatitude(), "N", "S");
String lon_text = AltosDroid.pos(receiver.getLongitude(), "E", "W");
AltosDebug.debug("lat %s lon %s\n", lat_text, lon_text);
receiver_latitude_view.setText(lat_text);
receiver_longitude_view.setText(lon_text);
set_value(receiver_altitude_view, AltosConvert.height, 1, altitude);
}
}
}

View File

@ -0,0 +1,88 @@
/*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import org.altusmetrum.altoslib_14.*;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.location.Location;
public class TabRecover extends AltosDroidTab {
private TextView mBearingView;
private TextView mDirectionView;
private TextView mDistanceView;
private TextView mTargetLatitudeView;
private TextView mTargetLongitudeView;
private TextView mReceiverLatitudeView;
private TextView mReceiverLongitudeView;
private TextView mMaxHeightView;
private TextView mMaxSpeedView;
private TextView mMaxAccelView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
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);
mReceiverLatitudeView = (TextView) v.findViewById(R.id.receiver_lat_value);
mReceiverLongitudeView = (TextView) v.findViewById(R.id.receiver_lon_value);
mMaxHeightView = (TextView) v.findViewById(R.id.max_height_value);
mMaxSpeedView = (TextView) v.findViewById(R.id.max_speed_value);
mMaxAccelView = (TextView) v.findViewById(R.id.max_accel_value);
return v;
}
public String tab_name() { return AltosDroid.tab_recover_name; }
public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
if (from_receiver != null) {
mBearingView.setText(String.format("%1.0f°", from_receiver.bearing));
set_value(mDistanceView, AltosConvert.distance, 1, 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"));
mTargetLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W"));
}
if (receiver != null) {
mReceiverLatitudeView.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S"));
mReceiverLongitudeView.setText(AltosDroid.pos(receiver.getLongitude(), "E", "W"));
}
if (state != null) {
set_value(mMaxHeightView, AltosConvert.height, 1, state.max_height());
set_value(mMaxAccelView, AltosConvert.accel, 1, state.max_acceleration());
set_value(mMaxSpeedView, AltosConvert.speed, 1, state.max_speed());
}
}
}

View File

@ -0,0 +1,160 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.altusmetrum.AltosDroid;
import java.util.ArrayList;
import android.content.Context;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TabHost;
import android.widget.TabWidget;
/**
* This is a helper class that implements the management of tabs and all
* details of connecting a ViewPager with associated TabHost. It relies on a
* trick. Normally a tab host has a simple API for supplying a View or
* Intent that each tab will show. This is not sufficient for switching
* between pages. So instead we make the content part of the tab host
* 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
* view to show as the tab content. It listens to changes in tabs, and takes
* care of switch to the correct paged in the ViewPager whenever the selected
* tab changes.
*/
public class TabsAdapter extends FragmentPagerAdapter
implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
private final Context mContext;
private final TabHost mTabHost;
private final ViewPager mViewPager;
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
private int position;
static class TabInfo {
private final String tag;
private final Class<?> clss;
private final Bundle args;
private Fragment fragment;
TabInfo(String _tag, Class<?> _class, Bundle _args, Fragment _fragment) {
tag = _tag;
clss = _class;
args = _args;
fragment = _fragment;
}
}
static class DummyTabFactory implements TabHost.TabContentFactory {
private final Context mContext;
public DummyTabFactory(Context context) {
mContext = context;
}
public View createTabContent(String tag) {
View v = new View(mContext);
v.setMinimumWidth(0);
v.setMinimumHeight(0);
return v;
}
}
public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) {
super(activity.getSupportFragmentManager());
mContext = activity;
mTabHost = tabHost;
mViewPager = pager;
mTabHost.setOnTabChangedListener(this);
mViewPager.setAdapter(this);
mViewPager.addOnPageChangeListener(this);
}
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args, Fragment fragment) {
tabSpec.setContent(new DummyTabFactory(mContext));
String tag = tabSpec.getTag();
TabInfo info = new TabInfo(tag, clss, args, fragment);
mTabs.add(info);
mTabHost.addTab(tabSpec);
notifyDataSetChanged();
}
@Override
public int getCount() {
return mTabs.size();
}
@Override
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
AltosDebug.debug("TabsAdapter.getItem(%d)", position);
if (info.fragment == null)
info.fragment = Fragment.instantiate(mContext, info.clss.getName(), info.args);
return info.fragment;
}
public Fragment currentItem() {
TabInfo info = mTabs.get(position);
return info.fragment;
}
public void onTabChanged(String tabId) {
AltosDroidTab prev_frag = (AltosDroidTab) mTabs.get(position).fragment;
position = mTabHost.getCurrentTab();
AltosDroidTab cur_frag = (AltosDroidTab) mTabs.get(position).fragment;
AltosDebug.debug("TabsAdapter.onTabChanged(%s) = %d cur %s prev %s", tabId, position, cur_frag, prev_frag);
if (prev_frag != cur_frag) {
if (prev_frag != null) {
prev_frag.set_visible(false);
}
}
/* This happens when the tab is selected before any of them
* have been created, like during rotation
*/
if (cur_frag != null)
cur_frag.set_visible(true);
mViewPager.setCurrentItem(position);
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
public void onPageSelected(int position) {
// Unfortunately when TabHost changes the current tab, it kindly
// also takes care of putting focus on it when not in touch mode.
// The jerk.
// This hack tries to prevent this from pulling focus out of our
// ViewPager.
TabWidget widget = mTabHost.getTabWidget();
int oldFocusability = widget.getDescendantFocusability();
widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
mTabHost.setCurrentTab(position);
widget.setDescendantFocusability(oldFocusability);
}
public void onPageScrollStateChanged(int state) {
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright © 2021 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, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.io.*;
import org.altusmetrum.altoslib_14.*;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Environment;
public class TelemetryLogger implements AltosLogTrace {
private TelemetryService service = null;
private AltosLink link = null;
private AltosLog logger = null;
private BroadcastReceiver mExternalStorageReceiver;
/* AltosLogTrace interface */
public void trace(String format, Object ... arguments) {
AltosDebug.debug(format, arguments);
}
public void open_failed(File file) {
service.send_file_failed_to_clients(file);
}
public TelemetryLogger(TelemetryService in_service, AltosLink in_link) {
service = in_service;
link = in_link;
startWatchingExternalStorage();
}
public void stop() {
stopWatchingExternalStorage();
close();
}
private void close() {
if (logger != null) {
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) {
AltosDebug.debug("Starting up Telemetry Logging");
logger = new AltosLog(link,this);
}
} else {
AltosDebug.debug("External Storage not present - stopping");
close();
}
}
void startWatchingExternalStorage() {
mExternalStorageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
handleExternalStorageState();
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
filter.addAction(Intent.ACTION_MEDIA_REMOVED);
service.registerReceiver(mExternalStorageReceiver, filter);
handleExternalStorageState();
}
void stopWatchingExternalStorage() {
service.unregisterReceiver(mExternalStorageReceiver);
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright © 2011 Keith Packard <keithp@keithp.com>
* Copyright © 2012 Mike Beattie <mike@ethernal.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.text.*;
import java.io.*;
import java.util.concurrent.*;
import android.os.Handler;
import org.altusmetrum.altoslib_14.*;
public class TelemetryReader extends Thread {
int crc_errors;
Handler handler;
AltosLink link;
LinkedBlockingQueue<AltosLine> telemQueue;
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);
return telem;
}
public void close() {
link.remove_monitor(telemQueue);
link = null;
telemQueue.clear();
telemQueue = null;
}
public void run() {
try {
AltosDebug.debug("starting loop");
while (telemQueue != null) {
try {
AltosTelemetry telem = read();
telem.set_frequency(link.frequency);
handler.obtainMessage(TelemetryService.MSG_TELEMETRY, telem).sendToTarget();
} catch (ParseException pp) {
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();
}
}
} 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) {
AltosDebug.debug("connected TelemetryReader create started");
link = in_link;
handler = in_handler;
telemQueue = new LinkedBlockingQueue<AltosLine>();
link.add_monitor(telemQueue);
link.set_telemetry(AltosLib.ao_telemetry_standard);
AltosDebug.debug("connected TelemetryReader created");
}
}

View File

@ -0,0 +1,748 @@
/*
* Copyright © 2012 Mike Beattie <mike@ethernal.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeoutException;
import java.io.*;
import java.util.*;
import android.app.*;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothAdapter;
import android.graphics.Color;
import android.hardware.usb.*;
import android.content.Intent;
import android.content.Context;
import android.os.*;
import android.os.Build.*;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import org.altusmetrum.altoslib_14.*;
public class TelemetryService extends Service implements AltosIdleMonitorListener {
static final int MSG_REGISTER_CLIENT = 1;
static final int MSG_UNREGISTER_CLIENT = 2;
static final int MSG_CONNECT = 3;
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;
static final int MSG_BLUETOOTH_ENABLED = 14;
static final int MSG_MONITOR_IDLE_START= 15;
static final int MSG_MONITOR_IDLE_STOP = 16;
static final int MSG_REBOOT = 17;
static final int MSG_IGNITER_QUERY = 18;
static final int MSG_IGNITER_FIRE = 19;
// 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;
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
DeviceAddress address;
private AltosDroidLink altos_link = null;
private TelemetryReader telemetry_reader = null;
private TelemetryLogger telemetry_logger = null;
// Local Bluetooth adapter
private BluetoothAdapter bluetooth_adapter = null;
// Last data seen; send to UI when it starts
private TelemetryState telemetry_state;
// Idle monitor if active
AltosIdleMonitor idle_monitor = null;
// Igniter bits
AltosIgnite ignite = null;
boolean ignite_running;
// Handler of incoming messages from clients.
static class IncomingHandler extends Handler {
private final WeakReference<TelemetryService> service;
IncomingHandler(TelemetryService s) { service = new WeakReference<TelemetryService>(s); }
@Override
public void handleMessage(Message msg) {
DeviceAddress address;
TelemetryService s = service.get();
AltosDroidLink bt = null;
if (s == null)
return;
switch (msg.what) {
/* Messages from application */
case MSG_REGISTER_CLIENT:
s.add_client(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
s.remove_client(msg.replyTo);
break;
case MSG_CONNECT:
AltosDebug.debug("Connect command received");
address = (DeviceAddress) msg.obj;
AltosDroidPreferences.set_active_device(address);
s.start_altos_bluetooth(address, false);
break;
case MSG_OPEN_USB:
AltosDebug.debug("Open USB command received");
UsbDevice device = (UsbDevice) msg.obj;
s.start_usb(device);
break;
case MSG_DISCONNECT:
AltosDebug.debug("Disconnect command received");
s.address = null;
if (!(Boolean) msg.obj)
AltosDroidPreferences.set_active_device(null);
s.disconnect(true);
break;
case MSG_DELETE_SERIAL:
AltosDebug.debug("Delete Serial command received");
s.delete_serial((Integer) msg.obj);
break;
case MSG_SETFREQUENCY:
AltosDebug.debug("MSG_SETFREQUENCY");
s.telemetry_state.frequency = (Double) msg.obj;
if (s.idle_monitor != null) {
s.idle_monitor.set_frequency(s.telemetry_state.frequency);
} else if (s.telemetry_state.connect == TelemetryState.CONNECT_CONNECTED) {
try {
s.altos_link.set_radio_frequency(s.telemetry_state.frequency);
s.altos_link.save_frequency();
} catch (InterruptedException e) {
} catch (TimeoutException e) {
}
}
s.send_to_clients();
break;
case MSG_SETBAUD:
AltosDebug.debug("MSG_SETBAUD");
s.telemetry_state.telemetry_rate = (Integer) msg.obj;
if (s.telemetry_state.connect == TelemetryState.CONNECT_CONNECTED) {
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);
}
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;
case MSG_BLUETOOTH_ENABLED:
AltosDebug.debug("TelemetryService notes that BT is now enabled");
address = AltosDroidPreferences.active_device();
if (address != null && !address.address.startsWith("USB"))
s.start_altos_bluetooth(address, false);
break;
case MSG_MONITOR_IDLE_START:
AltosDebug.debug("start monitor idle");
s.start_idle_monitor();
break;
case MSG_MONITOR_IDLE_STOP:
AltosDebug.debug("stop monitor idle");
s.stop_idle_monitor();
break;
case MSG_REBOOT:
AltosDebug.debug("reboot");
s.reboot_remote();
break;
case MSG_IGNITER_QUERY:
AltosDebug.debug("igniter query");
s.igniter_query(msg.replyTo);
break;
case MSG_IGNITER_FIRE:
AltosDebug.debug("igniter fire");
s.igniter_fire((String) msg.obj);
break;
default:
super.handleMessage(msg);
}
}
}
/* Handle telemetry packet
*/
private void telemetry(AltosTelemetry telem) {
AltosState state;
state = telemetry_state.get(telem.serial());
if (state == null)
state = new AltosState(new AltosCalData());
telem.provide_data(state);
telemetry_state.put(telem.serial(), state);
telemetry_state.quiet = false;
if (state != null) {
AltosPreferences.set_state(state,telem.serial());
}
send_to_clients();
}
/* Construct the message to deliver to clients
*/
private Message message() {
if (telemetry_state == null)
AltosDebug.debug("telemetry_state null!");
return Message.obtain(null, AltosDroid.MSG_STATE, telemetry_state);
}
/* 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);
send_idle_mode_to_client(client);
/* 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);
}
}
/* 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 = message();
try {
client.send(m);
} catch (RemoteException e) {
AltosDebug.error("Client %s disappeared", client.toString());
remove_client(client);
}
}
private void send_to_clients() {
for (Messenger client : clients)
send_to_client(client);
}
private void send_idle_mode_to_client(Messenger client) {
Message m = Message.obtain(null, AltosDroid.MSG_IDLE_MODE, idle_monitor != null);
try {
client.send(m);
} catch (RemoteException e) {
AltosDebug.error("Client %s disappeared", client.toString());
remove_client(client);
}
}
private void send_idle_mode_to_clients() {
for (Messenger client : clients)
send_idle_mode_to_client(client);
}
private void send_file_failed_to_client(Messenger client, File f) {
Message m = Message.obtain(null, AltosDroid.MSG_FILE_FAILED, f);
try {
client.send(m);
} catch (RemoteException e) {
AltosDebug.error("Client %s disappeared", client.toString());
remove_client(client);
}
}
public void send_file_failed_to_clients(File f) {
for (Messenger client : clients)
send_file_failed_to_client(client, f);
}
private void telemetry_start() {
if (telemetry_reader == null && idle_monitor == null && !ignite_running) {
telemetry_reader = new TelemetryReader(altos_link, handler);
telemetry_reader.start();
}
}
private void telemetry_stop() {
if (telemetry_reader != null) {
AltosDebug.debug("disconnect(): stopping TelemetryReader");
telemetry_reader.interrupt();
try {
telemetry_reader.join();
} catch (InterruptedException e) {
}
telemetry_reader = null;
}
}
private void disconnect(boolean notify) {
AltosDebug.debug("disconnect(): begin");
telemetry_state.connect = TelemetryState.CONNECT_DISCONNECTED;
telemetry_state.address = null;
if (idle_monitor != null)
stop_idle_monitor();
if (altos_link != null)
altos_link.closing();
stop_receiver_voltage_timer();
telemetry_stop();
if (telemetry_logger != null) {
AltosDebug.debug("disconnect(): stopping TelemetryLogger");
telemetry_logger.stop();
telemetry_logger = null;
}
if (altos_link != null) {
AltosDebug.debug("disconnect(): stopping AltosDroidLink");
altos_link.close();
altos_link = null;
ignite = null;
}
telemetry_state.config = null;
if (notify) {
AltosDebug.debug("disconnect(): send message to clients");
send_to_clients();
if (clients.isEmpty()) {
AltosDebug.debug("disconnect(): no clients, terminating");
stopSelf();
}
}
}
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.remove(serial);
AltosPreferences.remove_state(serial);
send_to_clients();
}
private void start_altos_bluetooth(DeviceAddress address, boolean pause) {
if (bluetooth_adapter == null || !bluetooth_adapter.isEnabled() || address.address == null)
return;
disconnect(false);
// Get the BluetoothDevice object
BluetoothDevice device = bluetooth_adapter.getRemoteDevice(address.address);
this.address = address;
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();
}
private void start_idle_monitor() {
if (altos_link != null && idle_monitor == null) {
telemetry_stop();
idle_monitor = new AltosIdleMonitor(this, altos_link, true, false);
idle_monitor.set_callsign(AltosPreferences.callsign());
idle_monitor.set_frequency(telemetry_state.frequency);
telemetry_state.idle_mode = true;
idle_monitor.start();
send_idle_mode_to_clients();
}
}
private void stop_idle_monitor() {
if (idle_monitor != null) {
try {
idle_monitor.abort();
} catch (InterruptedException ie) {
}
idle_monitor = null;
telemetry_state.idle_mode = false;
telemetry_start();
send_idle_mode_to_clients();
}
}
private void reboot_remote() {
if (altos_link != null) {
stop_idle_monitor();
try {
altos_link.start_remote();
altos_link.printf("r eboot\n");
altos_link.flush_output();
} catch (TimeoutException te) {
} catch (InterruptedException ie) {
} finally {
try {
altos_link.stop_remote();
} catch (InterruptedException ie) {
}
}
}
}
private void ensure_ignite() {
if (ignite == null)
ignite = new AltosIgnite(altos_link, true, false);
}
private synchronized void igniter_query(Messenger client) {
ensure_ignite();
HashMap<String,Integer> status_map = null;
ignite_running = true;
try {
stop_idle_monitor();
try {
status_map = ignite.status();
} catch (InterruptedException ie) {
AltosDebug.debug("ignite.status interrupted");
} catch (TimeoutException te) {
AltosDebug.debug("ignite.status timeout");
}
} finally {
ignite_running = false;
}
Message m = Message.obtain(null, AltosDroid.MSG_IGNITER_STATUS, status_map);
try {
client.send(m);
} catch (RemoteException e) {
}
}
private synchronized void igniter_fire(String igniter) {
ensure_ignite();
ignite_running = true;
stop_idle_monitor();
try {
ignite.fire(igniter);
} catch (InterruptedException ie) {
} finally {
ignite_running = false;
}
}
// Timer for receiver battery voltage monitoring
Timer receiver_voltage_timer;
private void update_receiver_voltage() {
if (altos_link != null && idle_monitor == null && !ignite_running) {
try {
double voltage = altos_link.monitor_battery();
telemetry_state.receiver_battery = voltage;
send_to_clients();
} 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 {
AltosDebug.debug("connected top");
AltosDebug.check_ui("connected\n");
try {
if (altos_link == null)
throw new InterruptedException("no bluetooth");
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.
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;
}
AltosDebug.debug("connected bluetooth configured");
telemetry_state.connect = TelemetryState.CONNECT_CONNECTED;
telemetry_state.address = address;
telemetry_start();
AltosDebug.debug("connected TelemetryReader started");
telemetry_logger = new TelemetryLogger(this, altos_link);
start_receiver_voltage_timer();
AltosDebug.debug("Notify UI of connection");
send_to_clients();
}
@Override
public void onCreate() {
AltosDebug.init(this);
// Initialise preferences
AltosDroidPreferences.init(this);
// Get local Bluetooth adapter
bluetooth_adapter = BluetoothAdapter.getDefaultAdapter();
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_DISCONNECTED;
telemetry_state.address = null;
/* Pull the saved state information out of the preferences database
*/
ArrayList<Integer> serials = AltosPreferences.list_states();
telemetry_state.latest_serial = AltosPreferences.latest_state();
telemetry_state.quiet = true;
AltosDebug.debug("latest serial %d\n", telemetry_state.latest_serial);
for (int serial : serials) {
AltosState saved_state = AltosPreferences.state(serial);
if (saved_state != null) {
AltosDebug.debug("recovered old state serial %d flight %d",
serial,
saved_state.cal_data().flight);
if (saved_state.gps != null)
AltosDebug.debug("\tposition %f,%f",
saved_state.gps.lat,
saved_state.gps.lon);
telemetry_state.put(serial, saved_state);
} else {
AltosDebug.debug("Failed to recover state for %d", serial);
AltosPreferences.remove_state(serial);
}
}
}
private String createNotificationChannel(String channelId, String channelName) {
NotificationChannel chan = new NotificationChannel(
channelId, channelName, NotificationManager.IMPORTANCE_NONE);
chan.setLightColor(Color.BLUE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
service.createNotificationChannel(chan);
return channelId;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
AltosDebug.debug("Received start id %d: %s", startId, intent);
int flag;
if (android.os.Build.VERSION.SDK_INT >= 31) // android.os.Build.VERSION_CODES.S
flag = 33554432; // PendingIntent.FLAG_MUTABLE
else
flag = 0;
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, AltosDroid.class), flag);
String channelId =
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
? createNotificationChannel("altosdroid_telemetry", "AltosDroid Telemetry Service")
: "";
// Create notification to be displayed while the service runs
Notification notification = new NotificationCompat.Builder(this, channelId)
.setContentTitle(getText(R.string.telemetry_service_label))
.setContentText(getText(R.string.telemetry_service_started))
.setContentIntent(contentIntent)
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.setSmallIcon(R.drawable.am_status_c)
// .setLargeIcon(R.drawable.am_status_c)
.build();
// 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;
}
@Override
public void onDestroy() {
// Stop the bluetooth Comms threads
disconnect(true);
// Demote us from the foreground, and cancel the persistent notification.
stopForeground(true);
// Tell the user we stopped.
Toast.makeText(this, R.string.telemetry_service_stopped, Toast.LENGTH_SHORT).show();
}
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
/* AltosIdleMonitorListener */
public void update(AltosState state, AltosListenerState listener_state) {
if (state != null)
AltosDebug.debug("update call %s freq %7.3f", state.cal_data().callsign, state.frequency);
telemetry_state.put(state.cal_data().serial, state);
telemetry_state.receiver_battery = listener_state.battery;
send_to_clients();
}
public void failed() {
}
public void error(String reason) {
stop_idle_monitor();
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright © 2012 Mike Beattie <mike@ethernal.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.util.*;
import org.altusmetrum.altoslib_14.*;
public class TelemetryState {
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;
int crc_errors;
double receiver_battery;
double frequency;
int telemetry_rate;
boolean idle_mode;
boolean quiet;
private HashMap<Integer,AltosState> states;
int latest_serial;
long latest_received_time;
public void put(int serial, AltosState state) {
long received_time = state.received_time;
if (received_time > latest_received_time || latest_serial == 0) {
latest_serial = serial;
latest_received_time = received_time;
}
states.put(serial, state);
}
public AltosState get(int serial) {
if (states.containsKey(serial))
return states.get(serial);
return null;
}
public void remove(int serial) {
states.remove((Integer) serial);
}
public Set<Integer> keySet() {
return states.keySet();
}
public Collection<AltosState> values() {
return states.values();
}
public boolean containsKey(int serial) {
return states.containsKey(serial);
}
public TelemetryState() {
connect = CONNECT_NONE;
config = null;
states = new HashMap<Integer,AltosState>();
crc_errors = 0;
receiver_battery = AltosLib.MISSING;
frequency = AltosPreferences.frequency(0);
telemetry_rate = AltosPreferences.telemetry_rate(0);
}
}

View File

@ -0,0 +1,201 @@
/*
* Copyright © 2020 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, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.altusmetrum.AltosDroid;
import java.lang.ref.WeakReference;
import java.util.*;
import android.Manifest;
import android.app.Activity;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.content.Context;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.content.DialogInterface;
import android.os.IBinder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.Parcelable;
import android.os.Parcel;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import android.view.*;
import android.widget.*;
import android.app.AlertDialog;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationListener;
import android.hardware.usb.*;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import org.altusmetrum.altoslib_14.*;
public class Tracker implements CharSequence, Comparable, Parcelable {
int serial;
String call;
double frequency;
long received_time;
String display;
private void make_display() {
if (frequency == 0.0)
display = "Auto";
else if (frequency == AltosLib.MISSING) {
display = String.format("%-8.8s %6d", call, serial);
} else {
display = String.format("%-8.8s %7.3f %6d", call, frequency, serial);
}
}
public Tracker(int serial, String call, double frequency, long received_time) {
if (call == null)
call = "none";
this.serial = serial;
this.call = call;
this.frequency = frequency;
this.received_time = received_time;
make_display();
}
public Tracker(int serial, String call, double frequency) {
this(serial, call, frequency, 0);
}
public Tracker(AltosState s) {
this(s == null ? 0 : s.cal_data().serial,
s == null ? null : s.cal_data().callsign,
s == null ? 0.0 : s.frequency,
s == null ? 0 : s.received_time);
}
/* CharSequence */
public char charAt(int index) {
return display.charAt(index);
}
public int length() {
return display.length();
}
public CharSequence subSequence(int start, int end) throws IndexOutOfBoundsException {
return display.subSequence(start, end);
}
public String toString() {
return display.toString();
}
/* Comparable */
public int compareTo (Object other) {
Tracker o = (Tracker) other;
if (frequency == 0.0) {
if (o.frequency == 0.0)
return 0;
return -1;
}
if (o.frequency == 0.0)
return 1;
int a = serial - o.serial;
int b = call.compareTo(o.call);
int c = (int) Math.signum(frequency - o.frequency);
if (b != 0)
return b;
if (c != 0)
return c;
return a;
}
/* Parcelable */
public int describeContents() {
AltosDebug.debug("describe contents %d", serial);
return 0;
}
public void writeToParcel(Parcel out, int flags) {
AltosDebug.debug("write to parcel %s", display);
out.writeInt(serial);
out.writeString(call);
out.writeDouble(frequency);
out.writeLong(received_time);
}
public static final Parcelable.Creator<Tracker> CREATOR
= new Parcelable.Creator<Tracker>() {
public Tracker createFromParcel(Parcel in) {
AltosDebug.debug("createFromParcel");
return new Tracker(in);
}
public Tracker[] newArray(int size) {
AltosDebug.debug("newArray %d", size);
return new Tracker[size];
}
};
/* newer (-1), same (0), older(1) */
public int compareAge(Tracker o) {
if (received_time == o.received_time)
return 0;
if (received_time == 0)
return -1;
if (o.received_time == 0)
return 1;
if (received_time > o.received_time)
return -1;
return 1;
}
public int compareCall(Tracker o) {
int v = call.compareTo(o.call);
if (v == 0)
return v;
if (call.equals("auto"))
return -1;
if (o.call.equals("auto"))
return 1;
return v;
}
public int compareSerial(Tracker o) {
return serial - o.serial;
}
public int compareFrequency(Tracker o) {
return (int) Math.signum(frequency - o.frequency);
}
private Tracker(Parcel in) {
serial = in.readInt();
call = in.readString();
frequency = in.readDouble();
received_time = in.readLong();
make_display();
AltosDebug.debug("Create from parcel %s", display);
}
}

View File

@ -0,0 +1,5 @@
all:
make -C ../../../../../../.. $@
%:
make -C ../../../../../../.. $@

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2012-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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
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" >
<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:id="@+id/callsign_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" >
<TextView
android:id="@+id/callsign_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/callsign_label" />
<TextView
android:id="@+id/callsign_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/callsign_label"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/serial_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" >
<TextView
android:id="@+id/serial_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/serial_label" />
<TextView
android:id="@+id/serial_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/serial_label"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/flight_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" >
<TextView
android:id="@+id/flight_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/flight_label" />
<TextView
android:id="@+id/flight_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/flight_label"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/state_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" >
<TextView
android:id="@+id/state_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/state_label" />
<TextView
android:id="@+id/state_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/state_label"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/rssi_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" >
<TextView
android:id="@+id/rssi_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rssi_label" />
<TextView
android:id="@+id/rssi_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/rssi_label"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/age_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" >
<TextView
android:id="@+id/age_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/age_label" />
<TextView
android:id="@+id/age_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/age_label"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
</LinearLayout>
<TabHost
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:orientation="horizontal" />
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0" />
<org.altusmetrum.AltosDroid.AltosViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
</TabHost>
<TextView
android:id="@+id/version"
android:layout_width="fill_parent"
android:layout_height="10dip"
android:layout_weight="0"
android:gravity="bottom|right"
android:textSize="7sp"
android:typeface="monospace" />
</LinearLayout>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
>
<TextView android:id="@+id/title_left_text"
android:layout_alignParentLeft="true"
android:ellipsize="end"
android:singleLine="true"
style="?android:attr/windowTitleStyle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
/>
<TextView android:id="@+id/title_right_text"
android:layout_alignParentRight="true"
android:ellipsize="end"
android:singleLine="true"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="#fff"
android:layout_weight="1"
/>
</RelativeLayout>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2016 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
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/button_scan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/button_scan"
/>
<TextView android:id="@+id/title_new_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/title_other_devices"
android:visibility="gone"
android:background="#666"
android:textColor="#fff"
android:paddingLeft="5dp"
/>
<ListView android:id="@+id/new_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android: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"
android:text="@string/title_paired_devices"
android:visibility="gone"
android:background="#666"
android:textColor="#fff"
android:paddingLeft="5dp"
/>
<ListView android:id="@+id/paired_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fadeScrollbars="false"
android:scrollbars="vertical"
/>
</LinearLayout>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
/>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2016 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
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="fill_parent"
android:orientation="horizontal"
>
<TextView
android:id="@+id/frequency"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:padding="10dp"
android:layout_weight="1"
/>
</LinearLayout>

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2016 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
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"
>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView android:id="@+id/set_callsign_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set_callsign_label"
/>
<EditText android:id="@+id/set_callsign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/set_callsign_label"/>
</LinearLayout>
<TextView android:id="@+id/frequency"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text=""
/>
<Button android:id="@+id/connect_idle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/connect_idle"
/>
<Button android:id="@+id/disconnect_idle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/disconnect_idle"
/>
<Button android:id="@+id/reboot_idle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/reboot_idle"
/>
<Button android:id="@+id/igniters_idle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/igniters_idle"
/>
</LinearLayout>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2016 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="@+id/igniter_status"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:padding="10dp"
android:layout_alignParentRight="true"
/>
<TextView
android:id="@+id/igniter_name"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dp"
android:layout_alignParentLeft="@+id/igniter_status"
/>
</RelativeLayout>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2016 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
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"
>
<ListView android:id="@+id/igniters"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fadeScrollbars="false"
android:scrollbars="vertical"
android:choiceMode="singleChoice"
/>
<ToggleButton android:id="@+id/igniter_arm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textOn="@string/igniter_armed"
android:textOff="@string/igniter_arm"
/>
<Button android:id="@+id/igniter_fire"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/igniter_fire"
/>
</LinearLayout>

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2016 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
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"
>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/set_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<EditText
android:id="@+id/set_frequency"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:padding="10dp"
android:layout_weight="1"
android:hint="@string/frequency"
android:inputType="number|numberDecimal"/>
/>
<TextView
android:id="@+id/mhz"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:padding="10dp"
android:layout_weight="0"
android:text="@string/mhz"
/>
<EditText
android:id="@+id/set_description"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:padding="10dp"
android:layout_weight="2"
android:hint="@string/description"
/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button android:id="@+id/set"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set"
android:layout_weight="1"
/>
<Button android:id="@+id/remove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/remove"
android:layout_weight="1"
/>
<Button android:id="@+id/done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/done"
android:layout_weight="1"
/>
</LinearLayout>
<ListView android:id="@+id/frequencies"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fadeScrollbars="false"
android:scrollbars="vertical"
android:choiceMode="singleChoice"
/>
</LinearLayout>

View File

@ -0,0 +1,132 @@
<?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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
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|numberSigned|numberDecimal"/>
<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|numberSigned|numberDecimal"/>
<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>

View File

@ -0,0 +1,48 @@
<?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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
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>

View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2016 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
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"
>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:stretchColumns="2,3"
android:layout_weight="0"
android:layout_width="fill_parent"
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">
<TextView
android:id="@+id/select_rate_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/telemetry_rate"
/>
<Spinner android:id="@+id/select_rate"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/telemetry_rate"
android:spinnerMode="dropdown"
/>
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/set_units_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set_units"
/>
<Spinner android:id="@+id/set_units"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/set_units"
android:spinnerMode="dropdown"
/>
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/font_size_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/font_size"
/>
<Spinner android:id="@+id/font_size"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/font_size"
android:spinnerMode="dropdown"
/>
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/map_type_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/map_type"
/>
<Spinner android:id="@+id/map_type"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/map_type"
android:spinnerMode="dropdown"
/>
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/map_source_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/map_source"
/>
<Spinner android:id="@+id/map_source"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/map_source"
android:spinnerMode="dropdown"
/>
</TableRow>
</TableLayout>
<Button android:id="@+id/preload_maps"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/preload_maps"
/>
<Button android:id="@+id/manage_frequencies"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/manage_frequencies"
/>
<Button android:id="@+id/done"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/done"
/>
</LinearLayout>

View File

@ -0,0 +1,430 @@
<?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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
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" >
<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"
>
<TextView
android:id="@+id/speed_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/speed_label" />
<TextView
android:id="@+id/speed_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/height_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
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:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/altitude_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/altitude_label" />
<TextView
android:id="@+id/altitude_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:id="@+id/tilt_view"
android:visibility="gone"
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/tilt_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/tilt_label" />
<TextView
android:id="@+id/tilt_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"
>
<TextView
android:id="@+id/max_speed_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/max_speed_label" />
<TextView
android:id="@+id/max_speed_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
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:layout_column="2"
android:text="@string/max_height_label" />
<TextView
android:id="@+id/max_height_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/max_altitude_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/max_altitude_label" />
<TextView
android:id="@+id/max_altitude_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/elevation_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/elevation_label" />
<TextView
android:id="@+id/elevation_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/range_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
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:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
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:layout_column="2"
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:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/compass_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_column="3"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
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:layout_column="2"
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:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/lat_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
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:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/lon_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
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:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:id="@+id/apogee_view"
android:visibility="gone"
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="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:contentDescription="@string/apogee_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/apogee_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
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: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" />
</TableRow>
<TableRow
android:id="@+id/main_view"
android:visibility="gone"
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="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/main_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/main_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
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" />
</TableRow>
</TableLayout>
</LinearLayout>

View File

@ -0,0 +1,15 @@
<?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:textColor="#ffffff"
android:gravity="center_horizontal"
android:background="#808080"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>

View File

@ -0,0 +1,151 @@
<?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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<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>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:stretchColumns="1,3"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:baselineAligned="true"
android:orientation="horizontal" >
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
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: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_weight="1"
android:text="" />
<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:text="" />
</TableRow>
</TableLayout>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:stretchColumns="1,2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:baselineAligned="true"
android:orientation="horizontal" >
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/target_pos_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="4dp"
android:text="@string/target_pos_label" />
<TextView
android:id="@+id/target_lat_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
<TextView
android:id="@+id/target_lon_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/receiver_pos_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="4dp"
android:text="@string/receiver_pos_label" />
<TextView
android:id="@+id/receiver_lat_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
<TextView
android:id="@+id/receiver_lon_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
</TableLayout>
</LinearLayout>

View File

@ -0,0 +1,565 @@
<?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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-->
<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="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/battery_voltage_label"
android:src="@drawable/grayled"
/>
<ImageView
android:id="@+id/battery_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
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: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="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/receiver_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/receiver_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
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="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/logging_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/logging_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
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="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/gps_locked_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/gps_locked_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
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="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/gps_ready_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/gps_ready_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
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="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/apogee_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/apogee_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
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="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/main_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/main_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
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="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/ignite_a_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/ignite_a_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
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="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/ignite_b_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/ignite_b_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
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="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/ignite_c_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/ignite_c_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
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="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/ignite_d_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/ignite_d_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
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:id="@+id/tilt_view"
android:visibility="gone"
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/tilt_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/tilt_label" />
<TextView
android:id="@+id/tilt_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>

View File

@ -0,0 +1,244 @@
<?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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
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" >
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:stretchColumns="0,1"
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"
>
<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:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
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:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
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:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
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:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
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:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
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:text="@string/receiver_latitude_label" />
<TextView
android:id="@+id/receiver_lat_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
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:text="@string/receiver_longitude_label" />
<TextView
android:id="@+id/receiver_lon_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""/>
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
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:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
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:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
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:text="" />
</TableRow>
</TableLayout>
</LinearLayout>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2020 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-->
<TableRow
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/call_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="3dp"
android:text="" />
<TextView
android:id="@+id/serial_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="3dp"
android:text="" />
<TextView
android:id="@+id/frequency_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="3dp"
android:text="" />
<TextView
android:id="@+id/age_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2020 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
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="match_parent"
android:layout_height="match_parent"
>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tracker_list"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stretchColumns="1,2,3,4">
<TableRow
android:id="@+id/tracker_row"
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<RadioButton
android:id="@+id/call_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/callsign_label" />
<RadioButton
android:id="@+id/serial_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/serial_label" />
<RadioButton
android:id="@+id/frequency_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/freq_label" />
<RadioButton
android:id="@+id/age_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/age_label" />
</TableRow>
</TableLayout>
</ScrollView>
</LinearLayout>

View File

@ -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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
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/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" />
<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/setup"
android:icon="@android:drawable/ic_menu_preferences"
android:title="@string/setup" />
<item android:id="@+id/idle_mode"
android:icon="@android:drawable/ic_menu_preferences"
android:title="@string/idle_mode" />
<item android:id="@+id/quit"
android:icon="@android:drawable/ic_menu_close_clear_cancel"
android:title="@string/quit" />
</menu>

View File

@ -0,0 +1,21 @@
<?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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-->
<resources>
<color name="old_color">#ffff4040</color>
</resources>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Small" parent="android:Theme.Material.Light">
<item name="android:textColor">#000000</item>
<item name="android:textSize">12sp</item>
<item name="android:lineHeight">14sp</item>
</style>
<style name="Medium" parent="android:Theme.Material.Light">
<item name="android:textColor">#000000</item>
<item name="android:textSize">15sp</item>
<item name="android:lineHeight">18sp</item>
</style>
<style name="Large" parent="android:Theme.Material.Light">
<item name="android:textColor">#000000</item>
<item name="android:textSize">20sp</item>
<item name="android:lineHeight">40sp</item>
</style>
<style name="Extra" parent="android:Theme.Material.Light">
<item name="android:textColor">#000000</item>
<item name="android:textSize">27sp</item>
<item name="android:lineHeight">32sp</item>
</style>
<style name="Small.Dialog" parent="android:Theme.Material.Light.Dialog">
<item name="android:textColor">#000000</item>
<item name="android:textSize">12sp</item>
<item name="android:lineHeight">14sp</item>
</style>
<style name="Medium.Dialog" parent="android:Theme.Material.Light.Dialog">
<item name="android:textColor">#000000</item>
<item name="android:textSize">15sp</item>
<item name="android:lineHeight">18sp</item>
</style>
<style name="Large.Dialog" parent="android:Theme.Material.Light.Dialog">
<item name="android:textColor">#000000</item>
<item name="android:textSize">20sp</item>
<item name="android:lineHeight">40sp</item>
</style>
<style name="Extra.Dialog" parent="android:Theme.Material.Light.Dialog">
<item name="android:textColor">#000000</item>
<item name="android:textSize">27sp</item>
<item name="android:lineHeight">32sp</item>
</style>
</resources>

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2012-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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-->
<resources>
<string name="app_name">AltosDroid</string>
<!-- AltosDroid -->
<string name="bt_not_enabled">Bluetooth was not enabled.</string>
<string name="title_connecting">connecting…</string>
<string name="title_connected_to">connected</string>
<string name="title_not_connected">not connected</string>
<!-- Options Menu -->
<string name="connect_device">Connect a device</string>
<string name="disconnect_device">Disconnect device</string>
<string name="quit">Quit</string>
<string name="setup">Setup</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">Map Source</string>
<!-- MapTypeActivity -->
<!-- <string name="map_type">Map Type</string> -->
<!-- DeviceListActivity -->
<string name="scanning">Scanning…</string>
<string name="select_device">Select device</string>
<string name="none_paired">No devices have been paired</string>
<string name="none_found">No devices found</string>
<string name="title_paired_devices">Paired Devices</string>
<string name="title_other_devices">Other Available Devices</string>
<string name="button_scan">Scan for devices</string>
<!-- TrackerListActivity -->
<string name="freq_label">Freq</string>
<!-- Service -->
<string name="telemetry_service_label">AltosDroid Telemetry Service</string>
<string name="telemetry_service_started">Telemetry Service Started</string>
<string name="telemetry_service_stopped">Telemetry Service Stopped</string>
<!-- UI fields -->
<!-- Header -->
<string name="callsign_label">Call</string>
<string name="serial_label">Serial</string>
<string name="flight_label">Flight</string>
<string name="state_label">State</string>
<string name="rssi_label">RSSI</string>
<string name="age_label">Age</string>
<!-- Tab fields -->
<string name="height_label">Height</string>
<string name="altitude_label">Altitude</string>
<string name="speed_label">Speed</string>
<string name="accel_label">Acceleration</string>
<string name="tilt_label">Tilt</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>
<string name="gnd_distance_label">Ground Distance</string>
<string name="max_height_label">Max Height</string>
<string name="max_altitude_label">Max Altitude</string>
<string name="max_speed_label">Max Speed</string>
<string name="max_accel_label">Max Accel</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>
<string name="longitude_label">Longitude</string>
<string name="target_pos_label">Tar</string>
<string name="receiver_pos_label">Me</string>
<string name="target_latitude_label">Tar Lat</string>
<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="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>
<!-- Idle mode -->
<string name="idle_mode">Idle Mode</string>
<string name="set_callsign_label">Callsign: </string>
<string name="connect_idle">Monitor</string>
<string name="disconnect_idle">Disconnect</string>
<string name="reboot_idle">Reboot</string>
<string name="igniters_idle">Fire Igniters</string>
<!-- igniters -->
<string name="igniters">Igniters</string>
<string name="igniter_arm">Arm</string>
<string name="igniter_armed">Armed</string>
<string name="igniter_fire">Fire</string>
<!-- setup -->
<string name="telemetry_rate">Telemetry Rate</string>
<string name="set_units">Units</string>
<!-- <string name="map_type">Map Type</string> -->
<!-- <string name="map_source">Map Source</string> -->
<!-- <string name="preload_maps">Preload Maps</string> -->
<string name="font_size">Text Size</string>
<string name="manage_frequencies">Manage Frequencies</string>
<string name="done">OK</string>
<!-- manage frequencies -->
<string name="set">Set</string>
<string name="mhz">MHz</string>
<string name="remove">Remove</string>
<!-- <string name="done">OK</string> -->
<string name="frequency">Frequency</string>
<string name="description">Description</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="65534" />
<usb-device vendor-id="1027" />
</resources>

27
altosdroid/build.gradle Normal file
View File

@ -0,0 +1,27 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

43
altosdroid/buildinfo.sh Executable file
View File

@ -0,0 +1,43 @@
#!/bin/sh
#
# Author: Mike Beattie <mike@ethernal.org>
#
# Script to parse result from git describe, and push values into
# BuildInfo.java for use within altosdroid (to display the current
# version and build information, primarily).
#
srcdir=app/src/main/java/org/altusmetrum/AltosDroid
infile=${srcdir}/BuildInfo.java.in
outfile=${srcdir}/BuildInfo.java
. ../src/Makedefs
version=$VERSION
branch=''
commitnum=''
commithash=''
builddate=$(date "+%Y-%m-%d")
buildtime=$(date "+%H:%M")
buildtz=$(date "+%z")
describe=$(git describe --match "$version" --long --always 2>/dev/null || echo '')
if [ -n "$describe" ]; then
branch=$(git branch | sed -ne 's/^\* //p')
commitdetails=$(echo $describe | sed -e "s/^$version-//")
commitnum=$(echo $commitdetails | cut -s -d- -f1)
commithash=$(echo $commitdetails | cut -d- -f2)
fi
echo "Version $describe, built on $builddate $buildtime $buildtz"
sed -e "s/@VERSION@/$version/" \
-e "s/@DESCRIBE@/$describe/" \
-e "s/@BRANCH@/$branch/" \
-e "s/@COMMITNUM@/$commitnum/" \
-e "s/@COMMITHASH@/$commithash/" \
-e "s/@BUILDDATE@/$builddate/" \
-e "s/@BUILDTIME@/$buildtime/" \
-e "s/@BUILDTZ@/$buildtz/" \
$infile > $outfile

View File

@ -0,0 +1,19 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Thu Aug 29 13:53:38 NZST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

172
altosdroid/gradlew vendored Executable file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
altosdroid/gradlew.bat vendored Normal file
View File

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,10 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must *NOT* be checked in Version Control Systems,
# as it contains information specific to your local configuration.
# location of the SDK. This is only used by Ant
# For customization when using a Version Control System, please read the
# header note.
sdk.dir=@ANDROID_SDK@

View File

@ -0,0 +1,7 @@
Version 1.5
* TeleGPS support
* Reloads previous flight data at startup
* Fixes crash when touching map
* Reconnect to TeleBT at startup
* Remember radio settings

View File

@ -0,0 +1 @@
include ':app'

4
altoslib/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
bin
classaltoslib.stamp
altoslib*.jar
AltosVersion.java

Some files were not shown because too many files have changed in this diff Show More