andbook!.pdf - Learning Android Get an anddev.org - Android-Shirt Back to index
anddev.org Header Logo
FAQ Search Top rated articles Browse Feeds anddev.org - Authors Contact Details Register Log in

TrackBuilder for mock location providers

Goto page 1, 2  Next
 
       anddev.org - Android Development Community | Android Tutorials | Index -> Map Tutorials
Author Message
jeremian
Freshman


Joined: 13 Dec 2007
Posts: 6

PostPosted: Thu Dec 27, 2007 10:41 pm    Post subject: TrackBuilder for mock location providers Reply with quote

TrackBuilder for mock location providers


What is this: This tutorial shows how to generate the track file for mock providers.

What you will learn:
  • How to open files and write to them.
  • How to display alerts from different threads
  • How to use submenus
  • How to transfer complex data between sub-activities


Problems/Questions:
  • Is it possible to manipulate files in /data/misc/location/ directly from the application?
  • Is it possible to 'reset' location providers?


Difficulty: 3 of 5

Usage:
1): We have to create the path. New points are added by pressing 'P' (or choosing the appropriate option from the menu).

2): After clicking 'Save track' (or pressing 'X') the new sub-activity is displayed. We have to enter the filename and speed of the recorded track (in microdegrees per second). I think 20 is a quick walk.

3): The track file is saved as 'filename_track' in the application's files directory.


4): We have to use adb in order to copy our track file to /data/misc/location/ directory. Here comes a small example, where I create the provider named 'test1' with the previously written track file.

a) We have to create /data/misc/location/test1 directory.
Code:

adb shell
# cd /data/misc/location
# mkdir test1
# exit

b) We have to download the track file from the emulator.
Code:

adb pull /data/data/net.mobilefight.trackbuilder/files/test1_track track

c) We have to upload the track file to /data/misc/location/test1/ directory.
Code:

adb push track /data/misc/location/test1/track


5): We can check the created track by choosing it from the 'GPS providers' submenu.


6): The violet dot is the location received from provider.


Description:
Because most of the code is taken from other tutorials from this site, I have decided to attach only full sources without detailed step-by-step description. However, if you have any questions, don't hesitate to ask. Any ideas will be welcomed.

The full source:

"/res/layout/main.xml":
XML:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

   
    <TextView  
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Hello World, TrackBuilder!"
    />

   
</LinearLayout>


"/res/layout/save_file.xml":
XML:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:padding="10dip">

   
    <TextView
        id="@+id/save_points_label1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/save_points_label1"
    />

   
    <EditText
        id="@+id/save_points_edit_returnvalue"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@android:drawable/editbox_background"
        android:singleLine="true"
        android:layout_below="@id/save_points_label1"
    />

       
    <TextView
        id="@+id/save_points_label2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/save_points_label2"
        android:layout_below="@id/save_points_edit_returnvalue"
    />

       
    <EditText
        id="@+id/save_points_edit_step"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@android:drawable/editbox_background"
        android:singleLine="true"
        android:layout_below="@id/save_points_label2"
        android:drawSelectorOnTop="true"
        android:numeric="true"
        android:text="@string/save_points_default_step"
    />


    <Button
        id="@+id/save_points_cmd_cancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/save_points_edit_step"
        android:layout_alignParentRight="true"
        android:layout_marginLeft="10dip"
        android:text="@string/save_points_cmd_cancel"
    />

   
    <Button
        id="@+id/save_points_cmd_save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toLeft="@id/save_points_cmd_cancel"
        android:layout_alignTop="@id/save_points_cmd_cancel"
        android:text="@string/save_points_cmd_save"
    />

       
</RelativeLayout>


"/res/layout/file_row.xml":
XML:

<?xml version="1.0" encoding="utf-8"?>
<TextView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="fill_parent"
     android:layout_height="wrap_content"
/>



"/res/values/strings.xml":
XML:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">TrackBuilder</string>
   
    <string name="map_menu_zoom_in">Zoom in (Key: I)</string>
    <string name="map_menu_zoom_out">Zoom out (Key: O)</string>
    <string name="map_menu_exit">Exit</string>
    <string name="map_menu_satellite">Toggle Satellite (Key: S)</string>
    <string name="map_menu_traffic">Toggle Traffic (Key: T)</string>
    <string name="map_menu_set_point">Set point (Key: P)</string>
    <string name="map_menu_remove_point">Remove last point (Key: R)</string>
    <string name="map_menu_clear_points">Clear all points (Key: C)</string>
    <string name="map_menu_providers">GPS providers</string>
    <string name="map_menu_save_points">Save track (Key: X)</string>
    <string name="map_menu_save_path">Save path</string>
    <string name="map_menu_load_path">Load path</string>        
    <string name="map_submenu_none">none</string>

    <string name="save_points_name">Save track</string>
    <string name="save_points_cmd_save">Save</string>
    <string name="save_points_cmd_cancel">Cancel</string>
    <string name="save_points_default_step">20</string>
    <string name="save_points_label1">Filename:</string>
    <string name="save_points_label2">Step (in microdegrees):</string>
   
    <string name="save_path_name">Save path</string>
    <string name="load_path_name">Load path</string>
</resources>


"AndroidManifest.xml":
XML:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.mobilefight.trackbuilder">

    <application android:icon="@drawable/icon">
        <activity class=".TrackBuilder" android:label="@string/app_name">
            <intent-filter>
                <action android:value="android.intent.action.MAIN" />
                <category android:value="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:value="android.intent.action.VIEW" />
                <category android:value="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <activity class=".SavePoints" android:label="@string/save_points_name" />
        <activity class=".LoadPath" android:label="@string/load_path_name" />
    </application>
</manifest>


"/src/your_package_structure/TrackBuilder.java":
Java:

package net.mobilefight.trackbuilder;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.OverlayController;
import com.google.android.maps.Point;

import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentReceiver;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Paint.Style;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.Looper;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.SubMenu;

public class TrackBuilder extends MapActivity {
     
     protected LocationManager myLocationManager = null;
     protected Location myLocation = null;
     protected MyIntentReceiver myIntentReceiver = new MyIntentReceiver();
     protected final long MINIMUM_DISTANCECHANGE_FOR_UPDATE = 5; // in Meters
    protected final long MINIMUM_TIME_BETWEEN_UPDATE = 1000; // in Milliseconds
     protected static final String LOCATION_CHANGED_ACTION =
     new String("android.intent.action.LOCATION_CHANGED");
    protected final IntentFilter myIntentFilter = new IntentFilter(LOCATION_CHANGED_ACTION);
    protected final Intent myIntent = new Intent(LOCATION_CHANGED_ACTION);
    protected boolean doUpdates = true;
   
    protected ProgressDialog myProgressDialog;
     
     protected MapView myMapView = null;
     protected MapController myMapController = null;
    protected OverlayController myOverlayController = null;
   
    protected List<Location> path = new ArrayList<Location>();
    protected SubMenu providers;
    protected LocationProvider provider;
   
    protected static final int SAVE_POINTS_REQUEST_CODE = 666;
    protected static final int SAVE_PATH_REQUEST_CODE = 667;
    protected static final int LOAD_PATH_REQUEST_CODE = 668;
   
    protected boolean providersLoaded = false;
    protected boolean loadingProviders = false;
    protected boolean loadedPath = false;
   
   
    class MyIntentReceiver extends IntentReceiver {
        @Override
        public void onReceiveIntent(Context context, Intent intent) {
             if(TrackBuilder.this.doUpdates)
                TrackBuilder.this.updateView();
        }
   }
   
    protected class MyLocationOverlay extends Overlay {
     @Override
     public void draw(Canvas canvas, PixelCalculator calculator, boolean shadow) {
          super.draw(canvas, calculator, shadow);
          Paint paint = new Paint();
          paint.setStyle(Style.FILL);
          
          paint.setARGB(255, 105, 105, 105);
          Point mapCentre = myMapView.getMapCenter();
          
          // we are always drawing the long/lat of the mapCentre
            canvas.drawText("longitude: " + mapCentre.getLongitudeE6() +
                    ", latitude: " + mapCentre.getLatitudeE6(),
                    5, 15, paint);
           
            // if there is GPS provider available, we draw the violet dot at the current
            // position and the long/lat at the top of the screen
          if (doUpdates && myLocation != null && provider != null) {                 
               Double lat = TrackBuilder.this.myLocation.getLatitude() * 1E6;
               Double lng = TrackBuilder.this.myLocation.getLongitude() * 1E6;
               Point point = new Point(lat.intValue(), lng.intValue());

               int[] myScreenCoords = new int[2];
               calculator.getPointXY(point, myScreenCoords);
               
               paint.setARGB(255, 80, 30, 150);
               canvas.drawOval(new RectF(myScreenCoords[0] - 5, myScreenCoords[1] + 5,
                        myScreenCoords[0] + 5, myScreenCoords[1] - 5), paint);
                canvas.drawText("longitude: " + point.getLongitudeE6() +
                         ", latitude: " + point.getLatitudeE6(),
                         5, 26, paint);
          }
          int[] prevScreenCoords = new int[2];
          boolean first = true;
          // we are iterating all the path's points and draw the green dots at the
          // points' positions and red lines between them
          for (Location loc : path) {
               Point point = new Point((int) loc.getLatitude(), (int) loc.getLongitude());          
               int[] screenCoords = new int[2];
               calculator.getPointXY(point, screenCoords);
               paint.setARGB(80, 156, 192, 36);
               canvas.drawOval(new RectF(screenCoords[0] - 5, screenCoords[1] + 5,
                        screenCoords[0] + 5, screenCoords[1] - 5), paint);
               if (!first) {
                    paint.setARGB(80, 255, 0, 0);
                    canvas.drawLine(prevScreenCoords[0], prevScreenCoords[1],
                            screenCoords[0], screenCoords[1], paint);
               }
               prevScreenCoords[0] = screenCoords[0];
               prevScreenCoords[1] = screenCoords[1];
               first = false;
          }
     }
    }
     
    @Override
    public void onCreate(Bundle icicle) {
     super.onCreate(icicle);
       
     myLocationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
        myMapView = new MapView(this);
        setContentView(myMapView);
        myMapController = this.myMapView.getController();        
        myOverlayController = this.myMapView.createOverlayController();
        MyLocationOverlay myLocationOverlay = new MyLocationOverlay();
        myOverlayController.add(myLocationOverlay, true);        
        myMapController.zoomTo(2);
    }
   
    @Override
    public void onResume() {
         super.onResume();
         doUpdates = true;
         registerReceiver(myIntentReceiver, myIntentFilter);
    }
   
    @Override
    public void onFreeze(Bundle icicle) {
         doUpdates = false;
         unregisterReceiver(myIntentReceiver);
         super.onFreeze(icicle);
    }
   
    private void updateView() {
     if (provider != null) {
          myLocation = myLocationManager.getCurrentLocation(provider.getName());
          myMapView.invalidate();
     }
    }
   
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
         boolean supRetVal = super.onCreateOptionsMenu(menu);
         menu.add(0, 0, getString(R.string.map_menu_zoom_in));
         menu.add(0, 1, getString(R.string.map_menu_zoom_out));
         menu.add(0, 2, getString(R.string.map_menu_satellite));
         menu.add(0, 3, getString(R.string.map_menu_traffic));
         menu.add(0, 4, getString(R.string.map_menu_set_point));
         menu.add(0, 5, getString(R.string.map_menu_remove_point));
         menu.add(0, 6, getString(R.string.map_menu_clear_points));
         // we remember the reference to submenu in order to modify it in the future
         providers = menu.addSubMenu(0, 7, getString(R.string.map_menu_providers));
         menu.add(0, 8, getString(R.string.map_menu_save_points));
         menu.addSeparator(0, 9);
         menu.add(0, 10, getString(R.string.map_menu_save_path));
         menu.add(0, 11, getString(R.string.map_menu_load_path));
         menu.addSeparator(0, 12);
         menu.add(0, 13, getString(R.string.map_menu_exit));
         return supRetVal;
    }
   

   
    @Override
    protected void onActivityResult(int requestCode, int resultCode,
          final String data, final Bundle extras) {
     super.onActivityResult(requestCode, resultCode, data, extras);
     switch (requestCode) {
     case SAVE_POINTS_REQUEST_CODE: // this is the result from save points subactivity
          if (resultCode == RESULT_OK) {
               // display the progress dialog
               myProgressDialog = ProgressDialog.show(TrackBuilder.this,
                         "Please wait...", "Saving track...", true);
               // start the thread, which will do the hard work
               new Thread() {
                    public void run() {
                         boolean resultOK = TrackFactory.saveFile(path, TrackBuilder.this, data, extras.getInteger("step"));
                         myProgressDialog.dismiss();
                         // we have to call Looper.prepare() before calling showAlert
                         Looper.prepare();
                         if (resultOK) {
                              showAlert("File saved", "Track successfully saved as " + data, "OK", false);
                         }
                         else {
                              showAlert("Error", "Track cannot be saved as " + data, "OK", false);
                         }
                         // in order to display alert dialog we have to call Looper.loop()
                         Looper.loop();
                         // after Looper.loop() we have to call quit() on our looper
                         Looper.myLooper().quit();
                    }
               }.start();
          }
          break;
     case SAVE_PATH_REQUEST_CODE:
          if (resultCode == RESULT_OK) {
               myProgressDialog = ProgressDialog.show(TrackBuilder.this,
                         "Please wait...", "Saving path...", true);
               new Thread() {
                    public void run() {
                         boolean resultOK = TrackFactory.savePath(path, TrackBuilder.this, data);
                         myProgressDialog.dismiss();
                         Looper.prepare();
                         if (resultOK) {
                              showAlert("File saved", "Path successfully saved as " + data, "OK", false);
                         }
                         else {
                              showAlert("Error", "Path cannot be saved as " + data, "OK", false);
                         }
                         Looper.loop();
                         Looper.myLooper().quit();
                    }
               }.start();
          }
          break;
     case LOAD_PATH_REQUEST_CODE:
          if (resultCode == RESULT_OK) {
               myProgressDialog = ProgressDialog.show(TrackBuilder.this,
                         "Please wait...", "Loading path...", true);
               loadedPath = false;
               new Thread() {
                    public void run() {
                         List<Location> newPath = new ArrayList<Location>();
                         boolean resultOK = TrackFactory.loadPath(newPath, TrackBuilder.this, data);
                         myProgressDialog.dismiss();
                         Looper.prepare();
                         if (resultOK) {
                              path.clear();
                              path.addAll(newPath);
                              showAlert("File loaded", "Path file " + data + " successfully loaded", "OK", false);
                         }
                         else {
                              showAlert("Error", "Path file " + data + " cannot be loaded", "OK", false);
                         }
                         // we have to release the main thread before calling Looper.lopp()!
                         loadedPath = true;
                         Looper.loop();
                         Looper.myLooper().quit();
                    }
               }.start();
               // we have to wait on the worker thread to invalidate the map view at the
               // proper moment
               while (true) {
                    if (loadedPath) {
                         break;
                    }                             
                    try {
                         Thread.sleep(500);
                    }
                    catch (InterruptedException e) {
                         // do nothing
                    }
               }
               // invalidate the map view in order to redraw the screen
               myMapView.invalidate();
          }
          else if (resultCode == LoadPath.RESULT_EMPTY) {
               showAlert("Error", "There are no saved path files!", "OK", false);
          }
          break;
     }
    }
   
    @Override
    public boolean onOptionsItemSelected(Menu.Item item){
     if (item.getGroup() == 0) { // main menu
          switch (item.getId()) {
          case 0: return zoomIn();
          case 1: return zoomOut();               
          case 2: return toggleSatellite();                 
          case 3: return toggleTraffic();                
          case 4: return setPoint();               
          case 5: return removePoint();
          case 6: return clearAllPoints();
          case 7: return updateProviders();
          case 8: return savePoints();
          case 10: return savePath();
          case 11: return loadPath();
          case 13: return exit();                 
          }
     }
     else { // submenu
          if (item.getId() == 0) { // none
               myLocationManager.removeUpdates(myIntent);
               doUpdates = false;
               myLocation = null;
               provider = null;
               myMapView.invalidate();
          }
          else {
               List<LocationProvider> locProviders = myLocationManager.getProviders();
               provider = locProviders.get(item.getId()-1);

               myLocationManager.requestUpdates(provider, MINIMUM_TIME_BETWEEN_UPDATE,
                         MINIMUM_DISTANCECHANGE_FOR_UPDATE, myIntent);
               onResume();
          }
     }
        return false;
    }
   
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
     switch (keyCode) {
     case KeyEvent.KEYCODE_I: return zoomIn();
     case KeyEvent.KEYCODE_O: return zoomOut();
     case KeyEvent.KEYCODE_S: return toggleSatellite();        
     case KeyEvent.KEYCODE_T: return toggleTraffic();
     case KeyEvent.KEYCODE_P: return setPoint();
     case KeyEvent.KEYCODE_R: return removePoint();
     case KeyEvent.KEYCODE_C: return clearAllPoints();
     case KeyEvent.KEYCODE_X: return savePoints();
     }
     return false;
    }
   
    // menu methods
    private boolean zoomIn() {
     this.myMapController.zoomTo(Math.min(21, this.myMapView.getZoomLevel() + 1));
     return true;
    }
   
    private boolean zoomOut() {
     this.myMapController.zoomTo(Math.max(1, this.myMapView.getZoomLevel() - 1));
        return true;
    }
   
    private boolean toggleSatellite() {
     myMapView.toggleSatellite();
         return true;
    }
   
    private boolean toggleTraffic() {
     myMapView.toggleTraffic();
        return true;
    }
   
    private boolean setPoint() {
     Point mapCentre = myMapView.getMapCenter();
     Location centreLocation = new Location();
     centreLocation.setLatitude(mapCentre.getLatitudeE6());
     centreLocation.setLongitude(mapCentre.getLongitudeE6());
     path.add(centreLocation);
     myMapView.invalidate();
     return true;
    }
   
    private boolean removePoint() {
     if (!path.isEmpty()) {
          path.remove(path.size()-1);
          myMapView.invalidate();
     }
     return true;
    }
   
    private boolean exit() {
     this.finish();
        return true;
    }
   
    private boolean updateProviders() {
     synchronized (this) {
          if (providersLoaded) {
               // the worker thread has finished and we simply update the
               // providers sumbenu with retrieved providers
          providers.removeGroup(1);
          providers.add(1, 0, getString(R.string.map_submenu_none));
               Collection<LocationProvider> locProviders = myLocationManager.getProviders();
               int counter = 1;
               for (LocationProvider provider : locProviders) {
                    providers.add(1, counter, provider.getName());
                    counter++;
               }
               return true;
          }
          else if (loadingProviders) { // the working thread hasn't finished yet
               return true;
          }
          // we have to set this flag at this moment to avoid creation of multiple threads
          loadingProviders = true;
     }

     myProgressDialog = ProgressDialog.show(TrackBuilder.this,
               "Please wait...", "Retrieving available providers...", true);
     new Thread() {
          // this working thread simply calls getProviders() method in order
          // to initialize the providers
          public void run() {
               myLocationManager.getProviders();
               myProgressDialog.dismiss();
               synchronized (TrackBuilder.this) {
                    providersLoaded = true;
               }                   
          }
     }.start();     
     return true;
    }
   
    private boolean clearAllPoints() {
     path.clear();
     this.myMapView.invalidate();
     return true;
    }
   
    private boolean savePoints() {
     if (path.size() < 2) {
          NotificationManager nm = (NotificationManager)
          getSystemService(NOTIFICATION_SERVICE);
          nm.notifyWithText(100, "You have to put at least two points on the map!",
                    NotificationManager.LENGTH_SHORT, null);
     }
     else {
          // start save points subactivity
          Intent i = new Intent(TrackBuilder.this, SavePoints.class);
          // we are adding the bundle to the intent with 'path' set to false
          Bundle extras = new Bundle();
          extras.putBoolean("path", false);
          i.putExtras(extras);
          startSubActivity(i, SAVE_POINTS_REQUEST_CODE);
     }
     return true;
    }
   
    private boolean savePath() {
     if (path.size() < 2) {
          NotificationManager nm = (NotificationManager)
          getSystemService(NOTIFICATION_SERVICE);
          nm.notifyWithText(100, "You have to put at least two points on the map!",
                    NotificationManager.LENGTH_SHORT, null);
     }
     else {
          Intent i = new Intent(TrackBuilder.this, SavePoints.class);
          // we are adding the bundle to the intent with 'path' set to true          
          Bundle extras = new Bundle();
          extras.putBoolean("path", true);
          i.putExtras(extras);
          startSubActivity(i, SAVE_PATH_REQUEST_CODE);
     }
     return true;
    }
   
    private boolean loadPath() {
     Intent i = new Intent(TrackBuilder.this, LoadPath.class);
          startSubActivity(i, LOAD_PATH_REQUEST_CODE);
          return true;
    }
}


"/src/your_package_structure/TrackFactory.java":
Java:

package net.mobilefight.trackbuilder;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;

import com.google.android.maps.Point;

import android.location.Location;

public class TrackFactory {
     
     private static long time = 0;
   
    public static double euclDistance(Point p1, Point p2) {
        return Math.sqrt(((double)p1.getLatitudeE6()-p2.getLatitudeE6())*((double)p1.getLatitudeE6()-p2.getLatitudeE6())
               + ((double)p1.getLongitudeE6()-p2.getLongitudeE6())*((double)p1.getLongitudeE6()-p2.getLongitudeE6()&