Home | History | Annotate | Download | only in shadows
      1 package com.xtremelabs.robolectric.shadows;
      2 
      3 import android.content.Context;
      4 import android.graphics.Point;
      5 import android.view.MotionEvent;
      6 import android.widget.ZoomButtonsController;
      7 import com.google.android.maps.GeoPoint;
      8 import com.google.android.maps.MapController;
      9 import com.google.android.maps.MapView;
     10 import com.google.android.maps.Overlay;
     11 import com.google.android.maps.Projection;
     12 import com.xtremelabs.robolectric.Robolectric;
     13 import com.xtremelabs.robolectric.internal.Implementation;
     14 import com.xtremelabs.robolectric.internal.Implements;
     15 
     16 import java.util.ArrayList;
     17 import java.util.List;
     18 
     19 import static com.xtremelabs.robolectric.RobolectricForMaps.shadowOf;
     20 
     21 /**
     22  * Shadow of {@code MapView} that simulates the internal state of a {@code MapView}. Supports {@code Projection}s,
     23  * {@code Overlay}s, and {@code TouchEvent}s
     24  */
     25 @SuppressWarnings({"UnusedDeclaration"})
     26 @Implements(MapView.class)
     27 public class ShadowMapView extends ShadowViewGroup {
     28     private boolean satelliteOn;
     29     private MapController mapController;
     30     private List<Overlay> overlays = new ArrayList<Overlay>();
     31     GeoPoint mapCenter = new GeoPoint(10, 10);
     32     int longitudeSpan = 20;
     33     int latitudeSpan = 30;
     34     int zoomLevel = 1;
     35     private ShadowMapController shadowMapController;
     36     private ZoomButtonsController zoomButtonsController;
     37     private MapView realMapView;
     38     private Projection projection;
     39     private boolean useBuiltInZoomMapControls;
     40     private boolean mouseDownOnMe = false;
     41     private Point lastTouchEventPoint;
     42     private GeoPoint mouseDownCenter;
     43     private boolean preLoadWasCalled;
     44     private boolean canCoverCenter = true;
     45 
     46     public ShadowMapView(MapView mapView) {
     47         realMapView = mapView;
     48         zoomButtonsController = new ZoomButtonsController(mapView);
     49     }
     50 
     51     public void __constructor__(Context context, String title) {
     52         super.__constructor__(context);
     53     }
     54 
     55     public static int toE6(double d) {
     56         return (int) (d * 1e6);
     57     }
     58 
     59     public static double fromE6(int i) {
     60         return i / 1e6;
     61     }
     62 
     63     @Implementation
     64     public void setSatellite(boolean satelliteOn) {
     65         this.satelliteOn = satelliteOn;
     66     }
     67 
     68     @Implementation
     69     public boolean isSatellite() {
     70         return satelliteOn;
     71     }
     72 
     73     @Implementation
     74     public boolean canCoverCenter() {
     75         return canCoverCenter;
     76     }
     77 
     78     @Implementation
     79     public MapController getController() {
     80         if (mapController == null) {
     81             try {
     82                 mapController = Robolectric.newInstanceOf(MapController.class);
     83                 shadowMapController = shadowOf(mapController);
     84                 shadowMapController.setShadowMapView(this);
     85             } catch (Exception e) {
     86                 throw new RuntimeException(e);
     87             }
     88         }
     89         return mapController;
     90     }
     91 
     92     @Implementation
     93     public ZoomButtonsController getZoomButtonsController() {
     94         return zoomButtonsController;
     95     }
     96 
     97     @Implementation
     98     public void setBuiltInZoomControls(boolean useBuiltInZoomMapControls) {
     99         this.useBuiltInZoomMapControls = useBuiltInZoomMapControls;
    100     }
    101 
    102     @Implementation
    103     public com.google.android.maps.Projection getProjection() {
    104         if (projection == null) {
    105             projection = new Projection() {
    106                 @Override public Point toPixels(GeoPoint geoPoint, Point point) {
    107                     if (point == null) {
    108                         point = new Point();
    109                     }
    110 
    111                     point.y = scaleDegree(geoPoint.getLatitudeE6(), bottom, top, mapCenter.getLatitudeE6(), latitudeSpan);
    112                     point.x = scaleDegree(geoPoint.getLongitudeE6(), left, right, mapCenter.getLongitudeE6(), longitudeSpan);
    113                     return point;
    114                 }
    115 
    116                 @Override public GeoPoint fromPixels(int x, int y) {
    117                     int lat = scalePixel(y, bottom, -realMapView.getHeight(), mapCenter.getLatitudeE6(), latitudeSpan);
    118                     int lng = scalePixel(x, left, realMapView.getWidth(), mapCenter.getLongitudeE6(), longitudeSpan);
    119                     return new GeoPoint(lat, lng);
    120                 }
    121 
    122                 @Override public float metersToEquatorPixels(float v) {
    123                     return 0;
    124                 }
    125             };
    126         }
    127         return projection;
    128     }
    129 
    130     private int scalePixel(int pixel, int minPixel, int maxPixel, int centerDegree, int spanDegrees) {
    131         int offsetPixels = pixel - minPixel;
    132         double ratio = offsetPixels / ((double) maxPixel);
    133         int minDegrees = centerDegree - spanDegrees / 2;
    134         return (int) (minDegrees + spanDegrees * ratio);
    135     }
    136 
    137     private int scaleDegree(int degree, int minPixel, int maxPixel, int centerDegree, int spanDegrees) {
    138         int minDegree = centerDegree - spanDegrees / 2;
    139         int offsetDegrees = degree - minDegree;
    140         double ratio = offsetDegrees / ((double) spanDegrees);
    141         int spanPixels = maxPixel - minPixel;
    142         return (int) (minPixel + spanPixels * ratio);
    143     }
    144 
    145     @Implementation
    146     public List<Overlay> getOverlays() {
    147         return overlays;
    148     }
    149 
    150     @Implementation
    151     public GeoPoint getMapCenter() {
    152         return mapCenter;
    153     }
    154 
    155     @Implementation
    156     public int getLatitudeSpan() {
    157         return latitudeSpan;
    158     }
    159 
    160     @Implementation
    161     public int getLongitudeSpan() {
    162         return longitudeSpan;
    163     }
    164 
    165     @Implementation
    166     public int getZoomLevel() {
    167         return zoomLevel;
    168     }
    169 
    170     @Implementation
    171     @Override public boolean dispatchTouchEvent(MotionEvent event) {
    172         for (Overlay overlay : overlays) {
    173             if (overlay.onTouchEvent(event, realMapView)) {
    174                 return true;
    175             }
    176         }
    177 
    178         GeoPoint mouseGeoPoint = getProjection().fromPixels((int) event.getX(), (int) event.getY());
    179         int diffX = 0;
    180         int diffY = 0;
    181         if (mouseDownOnMe) {
    182             diffX = (int) event.getX() - lastTouchEventPoint.x;
    183             diffY = (int) event.getY() - lastTouchEventPoint.y;
    184         }
    185 
    186         switch (event.getAction()) {
    187             case MotionEvent.ACTION_DOWN:
    188                 mouseDownOnMe = true;
    189                 mouseDownCenter = getMapCenter();
    190                 break;
    191             case MotionEvent.ACTION_MOVE:
    192                 if (mouseDownOnMe) {
    193                     moveByPixels(-diffX, -diffY);
    194                 }
    195                 break;
    196             case MotionEvent.ACTION_UP:
    197                 if (mouseDownOnMe) {
    198                     moveByPixels(-diffX, -diffY);
    199                     mouseDownOnMe = false;
    200                 }
    201                 break;
    202 
    203             case MotionEvent.ACTION_CANCEL:
    204                 getController().setCenter(mouseDownCenter);
    205                 mouseDownOnMe = false;
    206                 break;
    207         }
    208 
    209         lastTouchEventPoint = new Point((int) event.getX(), (int) event.getY());
    210 
    211         return super.dispatchTouchEvent(event);
    212     }
    213 
    214     @Implementation
    215     public void preLoad() {
    216         preLoadWasCalled = true;
    217     }
    218 
    219     private void moveByPixels(int x, int y) {
    220         Point center = getProjection().toPixels(mapCenter, null);
    221         center.offset(x, y);
    222         mapCenter = getProjection().fromPixels(center.x, center.y);
    223     }
    224 
    225     /**
    226      * Non-Android accessor.
    227      *
    228      * @return whether to use built in zoom map controls
    229      */
    230     public boolean getUseBuiltInZoomMapControls() {
    231         return useBuiltInZoomMapControls;
    232     }
    233 
    234     /**
    235      * Non-Android accessor.
    236      *
    237      * @return whether {@link #preLoad()} has been called on this {@code MapView}
    238      */
    239     public boolean preLoadWasCalled() {
    240         return preLoadWasCalled;
    241     }
    242 
    243     /**
    244      * Non-Android accessor to set the latitude span (the absolute value of the difference between the Northernmost and
    245      * Southernmost latitudes visible on the map) of this {@code MapView}
    246      *
    247      * @param latitudeSpan the new latitude span for this {@code MapView}
    248      */
    249     public void setLatitudeSpan(int latitudeSpan) {
    250         this.latitudeSpan = latitudeSpan;
    251     }
    252 
    253     /**
    254      * Non-Android accessor to set the longitude span (the absolute value of the difference between the Easternmost and
    255      * Westernmost longitude visible on the map) of this {@code MapView}
    256      *
    257      * @param longitudeSpan the new latitude span for this {@code MapView}
    258      */
    259     public void setLongitudeSpan(int longitudeSpan) {
    260         this.longitudeSpan = longitudeSpan;
    261     }
    262 
    263     /**
    264      * Non-Android accessor that controls the value to be returned by {@link #canCoverCenter()}
    265      *
    266      * @param canCoverCenter the value to be returned by {@link #canCoverCenter()}
    267      */
    268     public void setCanCoverCenter(boolean canCoverCenter) {
    269         this.canCoverCenter = canCoverCenter;
    270     }
    271 }
    272