PageRenderTime 63ms CodeModel.GetById 9ms app.highlight 48ms RepoModel.GetById 1ms app.codeStats 0ms

/compass/src/com/google/marvin/compass/TalkingCompass.java

http://eyes-free.googlecode.com/
Java | 569 lines | 431 code | 97 blank | 41 comment | 77 complexity | b038bb40d4c5dac29576d20297b3ffe8 MD5 | raw file
  1/*
  2 * Copyright (C) 2008 Google Inc.
  3 * 
  4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5 * use this file except in compliance with the License. You may obtain a copy of
  6 * the License at
  7 * 
  8 * http://www.apache.org/licenses/LICENSE-2.0
  9 * 
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 13 * License for the specific language governing permissions and limitations under
 14 * the License.
 15 */
 16
 17package com.google.marvin.compass;
 18
 19import com.google.marvin.compass.StreetLocator.StreetLocatorListener;
 20import com.google.tts.TTSEarcon;
 21import com.google.tts.TextToSpeechBeta;
 22import com.whereabout.location.LocationManager;
 23import com.whereabout.location.LocationManager.WifiLocalizationListener;
 24
 25import android.app.Activity;
 26import android.content.Context;
 27import android.graphics.Canvas;
 28import android.graphics.Color;
 29import android.graphics.Paint;
 30import android.graphics.Path;
 31import android.hardware.SensorListener;
 32import android.hardware.SensorManager;
 33import android.location.GpsStatus;
 34import android.location.Location;
 35import android.location.LocationListener;
 36import android.location.LocationProvider;
 37import android.os.Bundle;
 38import android.os.Vibrator;
 39import android.util.Config;
 40import android.util.Log;
 41import android.view.KeyEvent;
 42import android.view.MotionEvent;
 43import android.view.View;
 44
 45/**
 46 * Provides spoken feedback augmented by non-speech audio and tactile feedback
 47 * to turn an Android handset into a wearable compass.
 48 * 
 49 * @author clchen@google.com (Charles L. Chen)
 50 */
 51public class TalkingCompass extends Activity implements StreetLocatorListener {
 52    private static final int MUTE_SPEECHMODE = 0;
 53
 54    private static final int DEFAULT_SPEECHMODE = 1;
 55
 56    private static final int VERBOSE_SPEECHMODE = 2;
 57
 58    private static final int NUM_SPEECH_MODES = 3;
 59
 60    private static final int NORTH = 0;
 61
 62    private static final int EAST = 90;
 63
 64    private static final int SOUTH = 180;
 65
 66    private static final int WEST = 270;
 67
 68    private static final String[] DIRECTION_NAMES = {
 69            "north", "north north east", "north east", "east north east", "east",
 70            "east south east", "south east", "south south east", "south", "south south west",
 71            "south west", "west south west", "west", "west north west", "north west",
 72            "north north west", "north"
 73    };
 74
 75    private static final int TTS_MIN_VER = 1;
 76
 77    private static final String TAG = "Compass";
 78
 79    private static final int MIN_STABLECOUNT = 50;
 80
 81    private static final int STABLECOUNT_AFTER_MODESETTING = 25;
 82
 83    private static final int STABLECOUNT_FOR_VERBOSE = -100;
 84
 85    private static final int STABLECOUNT_FOR_CALIBRATION = -200;
 86
 87    // Degrees of tolerance for a reading to be considered stable
 88    private static final int STABLE_TOLERANCE = 5;
 89
 90    private static final int CARDINAL_TOLERANCE = 1;
 91
 92    private static final int NORTH_LEFT_MAX = 359;
 93
 94    private static final int NORTH_RIGHT_MAX = 1;
 95
 96    private static final long[] VIBE_PATTERN = {
 97            0, 1, 40, 41
 98    };
 99
100    private SensorManager sensorManager;
101
102    private CompassView view;
103
104    private float currentHeading;
105
106    private float lastStableHeading;
107
108    private int stableCount;
109
110    private int speechMode;
111
112    private int lastCardinalDir;
113
114    private Vibrator vibe;
115
116    private boolean sensorOk;
117
118    private TalkingCompass self;
119
120    private TextToSpeechBeta tts;
121
122    private long networkLocLastUpdateTime = -1;
123
124    private long gpsLocLastUpdateTime = -1;
125    
126    private long locationUpdateWaitTime = 15000;
127    private LocationManager locationManager;
128    private Location gpsLoc = null;
129
130    private Location networkLoc = null;
131
132    private StreetLocator locator = null;
133
134    private boolean usingGps = false;
135
136    private String currentAddress = "";
137
138    private String currentIntersection = "";
139    private String currentFrontBackStreets = "";
140
141    /**
142     * Handles the sensor events for changes to readings and accuracy
143     */
144    private final SensorListener mListener = new SensorListener() {
145        public void onSensorChanged(int sensor, float[] values) {
146            currentHeading = values[0]; // Values are yaw (heading), pitch, and
147            // roll.
148            if (view != null) {
149                view.invalidate();
150            }
151            processDirection();
152        }
153
154        public void onAccuracyChanged(int arg0, int arg1) {
155            sensorOk = (arg1 == SensorManager.SENSOR_STATUS_ACCURACY_HIGH);
156        }
157    };
158
159    private TextToSpeechBeta.OnInitListener ttsInitListener = new TextToSpeechBeta.OnInitListener() {
160        public void onInit(int status, int version) {
161
162            if (view != null) {
163                view.setVisibility(View.GONE);
164                view = null;
165            }
166            Log.e("Compass debug", 3 + "");
167            view = new CompassView(self);
168            setContentView(view);
169            stableCount = 0;
170            currentHeading = 0;
171            lastStableHeading = 0;
172            speechMode = DEFAULT_SPEECHMODE;
173            lastCardinalDir = 0;
174            tts.speak(getString(R.string.compass), 0, null);
175            Log.e("Compass debug", 4 + "");
176        }
177    };
178
179    private LocationListener networkLocationListener = new LocationListener() {
180        public void onLocationChanged(Location arg0) {
181            networkLoc = arg0;
182            if (networkLocLastUpdateTime + locationUpdateWaitTime < System.currentTimeMillis()){
183                updateLocationString();
184                networkLocLastUpdateTime = System.currentTimeMillis();
185            }
186            Log.e("Network location", "Lat: " + arg0.getLatitude() + ", Long: "
187                    + arg0.getLongitude());
188            Log.e("Network location", "Accuracy: " + arg0.getAccuracy());
189        }
190
191        public void onProviderDisabled(String arg0) {
192            unregisterLocationServices();
193            networkLoc = null;
194            networkLocLastUpdateTime = -1;
195        }
196
197        public void onProviderEnabled(String arg0) {
198        }
199
200        public void onStatusChanged(String arg0, int arg1, Bundle arg2) {
201        }
202
203    };
204
205    private LocationListener gpsLocationListener = new LocationListener() {
206        public void onLocationChanged(Location arg0) {
207            gpsLoc = arg0;
208            if (gpsLocLastUpdateTime + locationUpdateWaitTime < System.currentTimeMillis()){
209                updateLocationString();
210                gpsLocLastUpdateTime = System.currentTimeMillis();
211            }
212            Log.e("GPS location", "Lat: " + arg0.getLatitude() + ", Long: " + arg0.getLongitude());
213            Log.e("GPS location", "Accuracy: " + arg0.getAccuracy());
214        }
215
216        public void onProviderDisabled(String arg0) {
217            unregisterLocationServices();
218            gpsLoc = null;
219            gpsLocLastUpdateTime = -1;
220        }
221
222        public void onProviderEnabled(String arg0) {
223        }
224
225        public void onStatusChanged(String arg0, int arg1, Bundle arg2) {
226        }
227    };
228    
229    GpsStatus.Listener mGpsStatusListener = new GpsStatus.Listener(){
230        public void onGpsStatusChanged(int event) {
231            // TODO Auto-generated method stub
232            
233        }        
234    };
235    
236
237    @Override
238    protected void onCreate(Bundle icicle) {
239        super.onCreate(icicle);
240        self = this;
241        vibe = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
242        sensorOk = true;
243        sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
244        tts = new TextToSpeechBeta(this, ttsInitListener);
245        locator = new StreetLocator(this);
246        locationManager = new LocationManager(this);
247        locationManager.requestLocationUpdates(android.location.LocationManager.GPS_PROVIDER, 1000, 1,
248                gpsLocationListener);
249        locationManager.requestLocationUpdates(android.location.LocationManager.NETWORK_PROVIDER, 0, 0,
250                networkLocationListener);
251        locationManager.addGpsStatusListener(mGpsStatusListener);     
252    }
253
254    @Override
255    protected void onResume() {
256        if (Config.LOGD) {
257            Log.d(TAG, "onResume");
258        }
259        super.onResume();
260        sensorManager.registerListener(mListener, SensorManager.SENSOR_ORIENTATION,
261                SensorManager.SENSOR_DELAY_GAME);
262    }
263
264    @Override
265    protected void onStop() {
266        if (Config.LOGD) {
267            Log.d(TAG, "onStop");
268        }
269        sensorManager.unregisterListener(mListener);
270        super.onStop();
271    }
272
273    @Override
274    protected void onDestroy() {
275        tts.shutdown();
276        unregisterLocationServices();
277        locationManager.shutdown();
278        super.onDestroy();
279    }
280
281    private void updateLocationString() {
282        Location currentLocation = null;
283        long gpsTimeAdvantage = 300000;
284
285        if (gpsLocLastUpdateTime + gpsTimeAdvantage > networkLocLastUpdateTime) {
286            currentLocation = gpsLoc;
287            usingGps = true;
288        } else {
289            currentLocation = networkLoc;
290            usingGps = false;
291        }
292        
293        //currentLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
294        //usingGps = true;
295
296        if (currentLocation != null) {
297            locator.getAddressAsync(currentLocation.getLatitude(), currentLocation.getLongitude());
298            locator.getStreetIntersectionAsync(currentLocation.getLatitude(), currentLocation
299                    .getLongitude());
300            locator.getStreetsInFrontAndBackAsync(currentLocation.getLatitude(), currentLocation
301                    .getLongitude(), currentHeading);
302        }
303    }
304
305    public void onAddressLocated(String address) {
306        currentAddress = "";
307        if (address.length() > 0) {
308            // Drop the country
309            address = address.substring(0, address.lastIndexOf(","));
310            // Extract the state and zip and insert spaces in the state name
311            // that the synthesizer will do the right thing.
312            String rawStateZip = address.substring(address.lastIndexOf(",") + 1);
313            String zip = rawStateZip.substring(rawStateZip.lastIndexOf(" ") + 1);
314            String state = rawStateZip.substring(0, rawStateZip.lastIndexOf(" ") + 1);
315            String stateZip = "";
316            for (int i = 0; i < state.length(); i++) {
317                stateZip = stateZip + state.charAt(i) + " ";
318            }
319            stateZip = stateZip + zip;
320            currentAddress = "Near " + address.substring(0, address.lastIndexOf(",")) + ". "
321                    + stateZip;
322        }
323        Log.e("currentAddress", currentAddress);
324    }
325
326    public void onIntersectionLocated(String[] streetnames) {
327        currentIntersection = "";
328        if (streetnames.length == 0) {
329            return;
330        }
331        currentIntersection = "";
332        for (String ad : streetnames) {
333            if (currentAddress.indexOf(ad) == -1) {
334                currentIntersection += ad + " and ";
335            }
336        }
337        if (currentIntersection.length() > 5) {
338            currentIntersection = currentIntersection
339                    .substring(0, currentIntersection.length() - 4);
340            currentIntersection = " Nearby streets are: " + currentIntersection;
341        }
342        if (currentIntersection.indexOf("Unknown road") != -1){
343            currentIntersection = "";
344        }
345        Log.e("currentIntersection", currentIntersection);
346    }
347
348    public void onFrontBackLocated(String[] streetsFront, String[] streetsBack) {
349        currentFrontBackStreets = "";
350        String currentFrontStreet = "";
351        String currentBackStreet = "";
352        if (streetsFront.length > 0) {
353            for (String ad : streetsFront) {
354                if (currentAddress.indexOf(ad) == -1) {
355                    currentFrontStreet += ad + " and ";
356                }
357            }
358            if (currentFrontStreet.length() > 5) {
359                currentFrontStreet = currentFrontStreet.substring(0,
360                        currentFrontStreet.length() - 4);
361                currentFrontStreet = "Ahead. " + currentFrontStreet;
362            }
363        }
364        if (currentFrontStreet.indexOf("Unknown road") != -1){
365            currentFrontStreet = "";
366        }
367
368        if (streetsBack.length > 0) {
369            for (String ad : streetsBack) {
370                if (currentAddress.indexOf(ad) == -1) {
371                    currentBackStreet += ad + " and ";
372                }
373            }
374            if (currentBackStreet.length() > 5) {
375                currentBackStreet = currentBackStreet.substring(0,
376                        currentBackStreet.length() - 4);
377                currentBackStreet = " Behind. " + currentBackStreet;
378            }
379        }
380        if (currentBackStreet.indexOf("Unknown road") != -1){
381            currentBackStreet = "";
382        }
383        
384        currentFrontBackStreets = currentFrontStreet + currentBackStreet;
385        Log.e("currentFrontBackStreets", currentFrontBackStreets);
386    }
387
388    protected void processDirection() {
389        // Do not speak immediately - wait until the sensor readings have been
390        // stable for some time.
391        
392        if (Math.abs(lastStableHeading - currentHeading) < STABLE_TOLERANCE) {
393            stableCount++;
394        } else {
395            lastStableHeading = currentHeading;
396            stableCount = 0;
397        }
398//        if (stableCount > MIN_STABLECOUNT) {
399//            speakDirection();
400//        }
401
402        // Do not try bother determining if a new cardinal direction
403        // was reached if the sensors are not functioning correctly.
404        if (!sensorOk) {
405            return;
406        }
407        boolean newCardinalDir = false;
408        int candidateCardinal = findCardinalDir(currentHeading);
409
410        if (candidateCardinal != lastCardinalDir) {
411            newCardinalDir = true;
412            lastCardinalDir = candidateCardinal;
413        }
414
415        if (newCardinalDir) {
416            vibe.vibrate(VIBE_PATTERN, -1);
417        }
418    }
419
420    private int findCardinalDir(float heading) {
421        if ((heading > NORTH_LEFT_MAX) || (heading < NORTH_RIGHT_MAX)) {
422            return NORTH;
423        } else if ((heading > EAST - CARDINAL_TOLERANCE) && (heading < EAST + CARDINAL_TOLERANCE)) {
424            return EAST;
425        } else if ((heading > SOUTH - CARDINAL_TOLERANCE) && (heading < SOUTH + CARDINAL_TOLERANCE)) {
426            return SOUTH;
427        } else if ((heading > WEST - CARDINAL_TOLERANCE) && (heading < WEST + CARDINAL_TOLERANCE)) {
428            return WEST;
429        } else {
430            return -1;
431        }
432    }
433
434    public void setAndSpeakCurrentMode(int newSpeechMode) {
435        speechMode = (newSpeechMode + NUM_SPEECH_MODES) % NUM_SPEECH_MODES;
436        String text = "";
437        switch (speechMode) {
438            case VERBOSE_SPEECHMODE:
439                stableCount = STABLECOUNT_AFTER_MODESETTING;
440                text = getString(R.string.verbose);
441                break;
442            case DEFAULT_SPEECHMODE:
443                stableCount = STABLECOUNT_AFTER_MODESETTING;
444                text = getString(R.string.default_);
445                break;
446            case MUTE_SPEECHMODE:
447                text = getString(R.string.muted);
448                break;
449        }
450        tts.speak(text, 0, null);
451    }
452
453    public void speakDirection() {
454        stableCount = 0;
455        if (!sensorOk) {
456            tts.speak(getString(R.string.calibrate), 0, null);
457            stableCount = STABLECOUNT_FOR_CALIBRATION;
458            return;
459        }
460        if (speechMode == MUTE_SPEECHMODE) {
461            return;
462        }
463
464        tts.speak(directionToString(currentHeading), 0, null);
465        
466        String currentLocationUtterance = currentAddress + " " + currentIntersection + " " + currentFrontBackStreets;
467        if (currentLocationUtterance.length() > 2){
468            if (usingGps){
469                currentLocationUtterance = getString(R.string.gps) + currentLocationUtterance;
470            } else {
471                currentLocationUtterance = getString(R.string.network) + currentLocationUtterance;                
472            }
473            tts.speak(currentLocationUtterance, 1, null);
474        }
475
476        if (speechMode == VERBOSE_SPEECHMODE) {
477            stableCount = STABLECOUNT_FOR_VERBOSE;
478            int degrees = Math.round(currentHeading);
479            tts.speak(Integer.toString(degrees), 1, null);
480        }
481    }
482
483    public static String directionToString(float heading) {
484        int index = (int) ((heading * 100 + 1125) / 2250);
485        return DIRECTION_NAMES[index];
486    }
487
488    private void unregisterLocationServices() {
489        LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
490        locationManager.removeUpdates(networkLocationListener);
491        locationManager.removeUpdates(gpsLocationListener);
492    }
493
494
495    
496    @Override
497    public boolean onKeyDown(int keyCode, KeyEvent event){
498        if (keyCode == KeyEvent.KEYCODE_SEARCH){
499            boolean localizationStarted = locationManager.localizeWithWifi(5000, new WifiLocalizationListener(){
500                public void onWifiLocalizationResult(int arg0, Location result) {
501                    final String locationName = result == null ? "Location cannot be determined." :
502                        result.getExtras().getStringArray(LocationManager.MATCH_LOCATIONS)[0];
503                    tts.speak(locationName, 0, null);
504                }
505            });
506            if (localizationStarted){
507                // TODO: Restore the previous speech mode after location name is spoken
508                speechMode = MUTE_SPEECHMODE;
509                tts.speak("Scanning", 0, null);
510            }
511        }
512        return super.onKeyDown(keyCode, event);
513    }
514    
515    private class CompassView extends View {
516        private Paint paint = new Paint();
517
518        private Path path = new Path();
519
520        private float downY;
521
522        public CompassView(Context context) {
523            super(context);
524
525            // Construct a wedge-shaped path
526            path.moveTo(0, -50);
527            path.lineTo(-20, 60);
528            path.lineTo(0, 50);
529            path.lineTo(20, 60);
530            path.close();
531        }
532
533        @Override
534        protected void onDraw(Canvas canvas) {
535            canvas.drawColor(Color.WHITE);
536
537            paint.setAntiAlias(true);
538            paint.setColor(Color.BLACK);
539            paint.setStyle(Paint.Style.FILL);
540
541            int w = canvas.getWidth();
542            int h = canvas.getHeight();
543            int cx = w / 2;
544            int cy = h / 2;
545
546            canvas.translate(cx, cy);
547            canvas.rotate(-currentHeading);
548            canvas.drawPath(path, paint);
549        }
550
551        @Override
552        public boolean onTouchEvent(MotionEvent event) {
553            if (event.getAction() == MotionEvent.ACTION_DOWN) {
554                downY = event.getY();
555                return true;
556            } else if (event.getAction() == MotionEvent.ACTION_UP) {
557                if (event.getY() + 100 < downY) {
558                    setAndSpeakCurrentMode(speechMode + 1);
559                } else if (event.getY() - 100 > downY) {
560                    setAndSpeakCurrentMode(speechMode - 1);
561                } else {
562                    speakDirection();
563                }
564                return true;
565            }
566            return false;
567        }
568    }
569}