Move java source, and resources to new paths for gradle
[fw/altos] / altosdroid / app / src / main / java / org / altusmetrum / AltosDroid / AltosVoice.java
1 /*
2  * Copyright © 2011 Keith Packard <keithp@keithp.com>
3  * Copyright © 2012 Mike Beattie <mike@ethernal.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
18  */
19
20 package org.altusmetrum.AltosDroid;
21
22 import android.speech.tts.TextToSpeech;
23 import android.speech.tts.TextToSpeech.OnInitListener;
24 import android.location.Location;
25
26 import org.altusmetrum.altoslib_13.*;
27
28 public class AltosVoice {
29
30         private TextToSpeech tts         = null;
31         private boolean      tts_enabled = false;
32
33         static final int TELL_MODE_NONE = 0;
34         static final int TELL_MODE_PAD = 1;
35         static final int TELL_MODE_FLIGHT = 2;
36         static final int TELL_MODE_RECOVER = 3;
37
38         static final int TELL_FLIGHT_NONE = 0;
39         static final int TELL_FLIGHT_STATE = 1;
40         static final int TELL_FLIGHT_SPEED = 2;
41         static final int TELL_FLIGHT_HEIGHT = 3;
42         static final int TELL_FLIGHT_TRACK = 4;
43
44         private int             last_tell_mode;
45         private int             last_tell_serial = AltosLib.MISSING;
46         private int             last_state;
47         private AltosGPS        last_gps;
48         private double          last_height = AltosLib.MISSING;
49         private Location        last_receiver;
50         private long            last_speak_time;
51         private int             last_flight_tell = TELL_FLIGHT_NONE;
52         private boolean         quiet = false;
53
54         private long now() {
55                 return System.currentTimeMillis();
56         }
57
58         private void reset_last() {
59                 last_tell_mode = TELL_MODE_NONE;
60                 last_speak_time = now() - 100 * 1000;
61                 last_gps = null;
62                 last_height = AltosLib.MISSING;
63                 last_receiver = null;
64                 last_state = AltosLib.ao_flight_invalid;
65                 last_flight_tell = TELL_FLIGHT_NONE;
66         }
67
68         public AltosVoice(AltosDroid a) {
69                 tts = new TextToSpeech(a, new OnInitListener() {
70                         public void onInit(int status) {
71                                 if (status == TextToSpeech.SUCCESS) tts_enabled = true;
72                         }
73                 });
74                 reset_last();
75         }
76
77         public synchronized void set_enable(boolean enable) {
78                 tts_enabled = enable;
79         }
80
81         public synchronized void speak(String s) {
82                 if (!tts_enabled) return;
83                 last_speak_time = now();
84                 if (!quiet)
85                         tts.speak(s, TextToSpeech.QUEUE_ADD, null);
86         }
87
88         public synchronized long time_since_speak() {
89                 return now() - last_speak_time;
90         }
91
92         public synchronized void speak(String format, Object ... arguments) {
93                 speak(String.format(format, arguments));
94         }
95
96         public synchronized boolean is_speaking() {
97                 return tts.isSpeaking();
98         }
99
100         public void stop() {
101                 if (tts != null) {
102                         tts.stop();
103                         tts.shutdown();
104                 }
105         }
106
107         private boolean         last_apogee_good;
108         private boolean         last_main_good;
109         private boolean         last_gps_good;
110
111         private boolean tell_gonogo(String name,
112                                   boolean current,
113                                   boolean previous,
114                                   boolean new_mode) {
115                 if (current != previous || new_mode)
116                         speak("%s %s.", name, current ? "ready" : "not ready");
117                 return current;
118         }
119
120         private boolean tell_pad(TelemetryState telem_state, AltosState state,
121                               AltosGreatCircle from_receiver, Location receiver) {
122
123                 if (state == null)
124                         return false;
125
126                 AltosDebug.debug("tell_pad lag %b ltm %d\n", last_apogee_good, last_tell_mode);
127
128                 if (state.apogee_voltage != AltosLib.MISSING)
129                         last_apogee_good = tell_gonogo("apogee",
130                                                        state.apogee_voltage >= AltosLib.ao_igniter_good,
131                                                        last_apogee_good,
132                                                        last_tell_mode != TELL_MODE_PAD);
133
134                 if (state.main_voltage != AltosLib.MISSING)
135                         last_main_good = tell_gonogo("main",
136                                                      state.main_voltage >= AltosLib.ao_igniter_good,
137                                                      last_main_good,
138                                                      last_tell_mode != TELL_MODE_PAD);
139
140                 if (state.gps != null)
141                         last_gps_good = tell_gonogo("G P S",
142                                                     state.gps_ready,
143                                                     last_gps_good,
144                                                     last_tell_mode != TELL_MODE_PAD);
145                 return true;
146         }
147
148
149         private boolean descending(int state) {
150                 return AltosLib.ao_flight_drogue <= state && state <= AltosLib.ao_flight_landed;
151         }
152
153         private boolean target_moved(AltosState state) {
154                 if (last_gps != null && state != null && state.gps != null) {
155                         AltosGreatCircle        moved = new AltosGreatCircle(last_gps.lat, last_gps.lon, last_gps.alt,
156                                                                              state.gps.lat, state.gps.lon, state.gps.alt);
157                         double                  height_change = 0;
158                         double                  height = state.height();
159
160                         if (height != AltosLib.MISSING && last_height != AltosLib.MISSING)
161                                 height_change = Math.abs(last_height - height);
162
163                         if (moved.range < 10 && height_change < 10)
164                                 return false;
165                 }
166                 return true;
167         }
168
169         private boolean receiver_moved(Location receiver) {
170                 if (last_receiver != null && receiver != null) {
171                         AltosGreatCircle        moved = new AltosGreatCircle(last_receiver.getLatitude(),
172                                                                              last_receiver.getLongitude(),
173                                                                              last_receiver.getAltitude(),
174                                                                              receiver.getLatitude(),
175                                                                              receiver.getLongitude(),
176                                                                              receiver.getAltitude());
177                         if (moved.range < 10)
178                                 return false;
179                 }
180                 return true;
181         }
182
183         private boolean tell_flight(TelemetryState telem_state, AltosState state,
184                                     AltosGreatCircle from_receiver, Location receiver) {
185
186                 boolean spoken = false;
187
188                 if (state == null)
189                         return false;
190
191                 if (last_tell_mode != TELL_MODE_FLIGHT)
192                         last_flight_tell = TELL_FLIGHT_NONE;
193
194                 if (state.state() != last_state && AltosLib.ao_flight_boost <= state.state() && state.state() <= AltosLib.ao_flight_landed) {
195                         speak(state.state_name());
196                         if (descending(state.state()) && !descending(last_state)) {
197                                 if (state.max_height() != AltosLib.MISSING) {
198                                         speak("max height: %s.",
199                                               AltosConvert.height.say_units(state.max_height()));
200                                 }
201                         }
202                         last_flight_tell = TELL_FLIGHT_STATE;
203                         return true;
204                 }
205
206                 if (last_tell_mode == TELL_MODE_FLIGHT && last_flight_tell == TELL_FLIGHT_TRACK) {
207                         if (time_since_speak() < 10 * 1000)
208                                 return false;
209                         if (!target_moved(state) && !receiver_moved(receiver))
210                                 return false;
211                 }
212
213                 double  speed;
214                 double  height;
215
216                 if (last_flight_tell == TELL_FLIGHT_NONE || last_flight_tell == TELL_FLIGHT_STATE || last_flight_tell == TELL_FLIGHT_TRACK) {
217                         last_flight_tell = TELL_FLIGHT_SPEED;
218
219                         if (state.state() <= AltosLib.ao_flight_coast) {
220                                 speed = state.speed();
221                         } else {
222                                 speed = state.gps_speed();
223                                 if (speed == AltosLib.MISSING)
224                                         speed = state.speed();
225                         }
226
227                         if (speed != AltosLib.MISSING) {
228                                 speak("speed: %s.", AltosConvert.speed.say_units(speed));
229                                 return true;
230                         }
231                 }
232
233                 if (last_flight_tell == TELL_FLIGHT_SPEED) {
234                         last_flight_tell = TELL_FLIGHT_HEIGHT;
235                         height = state.height();
236
237                         if (height != AltosLib.MISSING) {
238                                 speak("height: %s.", AltosConvert.height.say_units(height));
239                                 return true;
240                         }
241                 }
242
243                 if (last_flight_tell == TELL_FLIGHT_HEIGHT) {
244                         last_flight_tell = TELL_FLIGHT_TRACK;
245                         if (from_receiver != null) {
246                                 speak("bearing %s %d, elevation %d, distance %s.",
247                                       from_receiver.bearing_words(
248                                               AltosGreatCircle.BEARING_VOICE),
249                                       (int) (from_receiver.bearing + 0.5),
250                                       (int) (from_receiver.elevation + 0.5),
251                                       AltosConvert.distance.say(from_receiver.distance));
252                                 return true;
253                         }
254                 }
255
256                 return spoken;
257         }
258
259         private boolean tell_recover(TelemetryState telem_state, AltosState state,
260                                      AltosGreatCircle from_receiver, Location receiver) {
261
262                 if (from_receiver == null)
263                         return false;
264
265                 if (last_tell_mode == TELL_MODE_RECOVER) {
266                         if (!target_moved(state) && !receiver_moved(receiver))
267                                 return false;
268                         if (time_since_speak() <= 10 * 1000)
269                                 return false;
270                 }
271
272                 String direction = AltosDroid.direction(from_receiver, receiver);
273                 if (direction == null)
274                         direction = String.format("Bearing %d", (int) (from_receiver.bearing + 0.5));
275
276                 speak("%s, distance %s.", direction,
277                       AltosConvert.distance.say_units(from_receiver.distance));
278
279                 return true;
280         }
281
282         public void tell(TelemetryState telem_state, AltosState state,
283                          AltosGreatCircle from_receiver, Location receiver,
284                          AltosDroidTab tab, boolean quiet) {
285
286                 this.quiet = quiet;
287
288                 boolean spoken = false;
289
290                 if (!tts_enabled) return;
291
292                 if (is_speaking()) return;
293
294                 int     tell_serial = last_tell_serial;
295
296                 if (state != null)
297                         tell_serial = state.cal_data().serial;
298
299                 if (tell_serial != last_tell_serial)
300                         reset_last();
301
302                 int     tell_mode = TELL_MODE_NONE;
303
304                 if (tab.tab_name().equals(AltosDroid.tab_pad_name))
305                         tell_mode = TELL_MODE_PAD;
306                 else if (tab.tab_name().equals(AltosDroid.tab_flight_name))
307                         tell_mode = TELL_MODE_FLIGHT;
308                 else
309                         tell_mode = TELL_MODE_RECOVER;
310
311                 if (tell_mode == TELL_MODE_PAD)
312                         spoken = tell_pad(telem_state, state, from_receiver, receiver);
313                 else if (tell_mode == TELL_MODE_FLIGHT)
314                         spoken = tell_flight(telem_state, state, from_receiver, receiver);
315                 else
316                         spoken = tell_recover(telem_state, state, from_receiver, receiver);
317
318                 if (spoken) {
319                         last_tell_mode = tell_mode;
320                         last_tell_serial = tell_serial;
321                         if (state != null) {
322                                 last_state = state.state();
323                                 last_height = state.height();
324                                 if (state.gps != null)
325                                         last_gps = state.gps;
326                         }
327                         if (receiver != null)
328                                 last_receiver = receiver;
329                 }
330         }
331 }