Merge "Do not use passive GPS data for COARSE only apps." into jb-mr1-dev
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 2d94ddc..672e378 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -59,6 +59,16 @@
      */
     public static final int FORMAT_SECONDS = 2;
 
+    /**
+     * @hide
+     */
+    public static final String EXTRA_COARSE_LOCATION = "coarseLocation";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
+
     private String mProvider;
     private long mTime = 0;
     private long mElapsedRealtimeNano = 0;
@@ -893,4 +903,36 @@
         parcel.writeFloat(mAccuracy);
         parcel.writeBundle(mExtras);
     }
+
+    /**
+     * Returns one of the optional extra {@link Location}s that can be attached
+     * to this Location.
+     *
+     * @param key the key associated with the desired extra Location
+     * @return the extra Location, or null if unavailable
+     * @hide
+     */
+    public Location getExtraLocation(String key) {
+        if (mExtras != null) {
+            Parcelable value = mExtras.getParcelable(key);
+            if (value instanceof Location) {
+                return (Location) value;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Attaches an extra {@link Location} to this Location.
+     *
+     * @param key the key associated with the Location extra
+     * @param location the Location to attach
+     * @hide
+     */
+    public void setExtraLocation(String key, Location value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putParcelable(key, value);
+    }
 }
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index b1863b8..f4f7b09 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -144,7 +144,7 @@
     private int mNumUpdates = Integer.MAX_VALUE;  // no expiry
     private float mSmallestDisplacement = 0.0f;    // meters
 
-    private String mProvider = null;  // for deprecated API's that explicitly request a provider
+    private String mProvider = LocationManager.FUSED_PROVIDER;  // for deprecated APIs that explicitly request a provider
 
     /**
      * Create a location request with default parameters.
diff --git a/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java b/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java
index 1c22c7a..b83521ae 100644
--- a/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java
+++ b/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java
@@ -302,6 +302,10 @@
                     0.0, 360.0));
         }
 
+        if (mNetworkLocation != null) {
+            fused.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, mNetworkLocation);
+        }
+
         mFusedLocation = fused;
 
         mCallback.reportLocation(mFusedLocation);
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 197f6ab..840d432 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -900,25 +900,41 @@
         return receiver;
     }
 
+    private boolean isProviderAllowedByCoarsePermission(String provider) {
+        if (LocationManager.FUSED_PROVIDER.equals(provider)) {
+            return true;
+        }
+        if (LocationManager.PASSIVE_PROVIDER.equals(provider)) {
+            return true;
+        }
+        if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
+            return true;
+        }
+        return false;
+    }
+
     private String checkPermissionAndRequest(LocationRequest request) {
         String perm = checkPermission();
 
         if (ACCESS_COARSE_LOCATION.equals(perm)) {
-             switch (request.getQuality()) {
-                 case LocationRequest.ACCURACY_FINE:
-                     request.setQuality(LocationRequest.ACCURACY_BLOCK);
-                     break;
-                 case LocationRequest.POWER_HIGH:
-                     request.setQuality(LocationRequest.POWER_LOW);
-                     break;
-             }
-             // throttle
-             if (request.getInterval() < LocationFudger.FASTEST_INTERVAL_MS) {
-                 request.setInterval(LocationFudger.FASTEST_INTERVAL_MS);
-             }
-             if (request.getFastestInterval() < LocationFudger.FASTEST_INTERVAL_MS) {
-                 request.setFastestInterval(LocationFudger.FASTEST_INTERVAL_MS);
-             }
+            if (!isProviderAllowedByCoarsePermission(request.getProvider())) {
+                throw new SecurityException("Requires ACCESS_FINE_LOCATION permission");
+            }
+            switch (request.getQuality()) {
+                case LocationRequest.ACCURACY_FINE:
+                    request.setQuality(LocationRequest.ACCURACY_BLOCK);
+                    break;
+                case LocationRequest.POWER_HIGH:
+                    request.setQuality(LocationRequest.POWER_LOW);
+                    break;
+            }
+            // throttle
+            if (request.getInterval() < LocationFudger.FASTEST_INTERVAL_MS) {
+                request.setInterval(LocationFudger.FASTEST_INTERVAL_MS);
+            }
+            if (request.getFastestInterval() < LocationFudger.FASTEST_INTERVAL_MS) {
+                request.setFastestInterval(LocationFudger.FASTEST_INTERVAL_MS);
+            }
         }
         // make getFastestInterval() the minimum of interval and fastest interval
         if (request.getFastestInterval() > request.getInterval()) {
@@ -990,7 +1006,9 @@
         // use the fused provider
         if (request == null) request = DEFAULT_LOCATION_REQUEST;
         String name = request.getProvider();
-        if (name == null) name = LocationManager.FUSED_PROVIDER;
+        if (name == null) {
+            throw new IllegalArgumentException("provider name must not be null");
+        }
         LocationProviderInterface provider = mProvidersByName.get(name);
         if (provider == null) {
             throw new IllegalArgumentException("provider doesn't exisit: " + provider);
@@ -1094,12 +1112,19 @@
             if (!isAllowedBySettingsLocked(name)) return null;
 
             Location location = mLastLocation.get(name);
+            if (location == null) {
+                return null;
+            }
             if (ACCESS_FINE_LOCATION.equals(perm)) {
                 return location;
             } else {
-                return mLocationFudger.getOrCreate(location);
+                Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
+                if (noGPSLocation != null) {
+                    return mLocationFudger.getOrCreate(noGPSLocation);
+                }
             }
         }
+        return null;
     }
 
     @Override
@@ -1329,17 +1354,29 @@
         LocationProviderInterface p = mProvidersByName.get(provider);
         if (p == null) return;
 
-        // Add the coarse location as an extra
-        Location coarse = mLocationFudger.getOrCreate(location);
-
         // Update last known locations
+        Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
+        Location lastNoGPSLocation = null;
         Location lastLocation = mLastLocation.get(provider);
         if (lastLocation == null) {
             lastLocation = new Location(provider);
             mLastLocation.put(provider, lastLocation);
+        } else {
+            lastNoGPSLocation = lastLocation.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
+            if (noGPSLocation == null && lastNoGPSLocation != null) {
+                // New location has no no-GPS location: adopt last no-GPS location. This is set
+                // directly into location because we do not want to notify COARSE clients.
+                location.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, lastNoGPSLocation);
+            }
         }
         lastLocation.set(location);
 
+        // Fetch coarse location
+        Location coarseLocation = null;
+        if (noGPSLocation != null && !noGPSLocation.equals(lastNoGPSLocation)) {
+            coarseLocation = mLocationFudger.getOrCreate(noGPSLocation);
+        }
+
         // Fetch latest status update time
         long newStatusUpdateTime = p.getStatusUpdateTime();
 
@@ -1361,29 +1398,31 @@
                 continue;
             }
 
+            Location notifyLocation = null;
             if (ACCESS_FINE_LOCATION.equals(receiver.mPermission)) {
-                location = lastLocation;  // use fine location
+                notifyLocation = lastLocation;  // use fine location
             } else {
-                location = coarse;  // use coarse location
+                notifyLocation = coarseLocation;  // use coarse location if available
             }
-
-            Location lastLoc = r.mLastFixBroadcast;
-            if ((lastLoc == null) || shouldBroadcastSafe(location, lastLoc, r)) {
-                if (lastLoc == null) {
-                    lastLoc = new Location(location);
-                    r.mLastFixBroadcast = lastLoc;
-                } else {
-                    lastLoc.set(location);
-                }
-                if (!receiver.callLocationChangedLocked(location)) {
-                    Slog.w(TAG, "RemoteException calling onLocationChanged on " + receiver);
-                    receiverDead = true;
+            if (notifyLocation != null) {
+                Location lastLoc = r.mLastFixBroadcast;
+                if ((lastLoc == null) || shouldBroadcastSafe(notifyLocation, lastLoc, r)) {
+                    if (lastLoc == null) {
+                        lastLoc = new Location(notifyLocation);
+                        r.mLastFixBroadcast = lastLoc;
+                    } else {
+                        lastLoc.set(notifyLocation);
+                    }
+                    if (!receiver.callLocationChangedLocked(notifyLocation)) {
+                        Slog.w(TAG, "RemoteException calling onLocationChanged on " + receiver);
+                        receiverDead = true;
+                    }
                 }
             }
 
             long prevStatusUpdateTime = r.mLastStatusBroadcast;
             if ((newStatusUpdateTime > prevStatusUpdateTime) &&
-                (prevStatusUpdateTime != 0 || status != LocationProvider.AVAILABLE)) {
+                    (prevStatusUpdateTime != 0 || status != LocationProvider.AVAILABLE)) {
 
                 r.mLastStatusBroadcast = newStatusUpdateTime;
                 if (!receiver.callStatusChangedLocked(provider, status, extras)) {
diff --git a/services/java/com/android/server/location/LocationFudger.java b/services/java/com/android/server/location/LocationFudger.java
index 84fd255..2a68743 100644
--- a/services/java/com/android/server/location/LocationFudger.java
+++ b/services/java/com/android/server/location/LocationFudger.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.location.Location;
+import android.location.LocationManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Parcelable;
@@ -40,8 +41,6 @@
     private static final boolean D = false;
     private static final String TAG = "LocationFudge";
 
-    private static final String EXTRA_COARSE_LOCATION = "coarseLocation";
-
     /**
      * Default coarse accuracy in meters.
      */
@@ -168,18 +167,10 @@
      */
     public Location getOrCreate(Location location) {
         synchronized (mLock) {
-            Bundle extras = location.getExtras();
-            if (extras == null) {
+            Location coarse = location.getExtraLocation(Location.EXTRA_COARSE_LOCATION);
+            if (coarse == null) {
                 return addCoarseLocationExtraLocked(location);
             }
-            Parcelable parcel = extras.getParcelable(EXTRA_COARSE_LOCATION);
-            if (parcel == null) {
-                return addCoarseLocationExtraLocked(location);
-            }
-            if (!(parcel instanceof Location)) {
-                return addCoarseLocationExtraLocked(location);
-            }
-            Location coarse = (Location) parcel;
             if (coarse.getAccuracy() < mAccuracyInMeters) {
                 return addCoarseLocationExtraLocked(location);
             }
@@ -188,11 +179,8 @@
     }
 
     private Location addCoarseLocationExtraLocked(Location location) {
-        Bundle extras = location.getExtras();
-        if (extras == null) extras = new Bundle();
         Location coarse = createCoarseLocked(location);
-        extras.putParcelable(EXTRA_COARSE_LOCATION, coarse);
-        location.setExtras(extras);
+        location.setExtraLocation(Location.EXTRA_COARSE_LOCATION, coarse);
         return coarse;
     }
 
diff --git a/services/java/com/android/server/location/PassiveProvider.java b/services/java/com/android/server/location/PassiveProvider.java
index 0ce21b7..71bae07 100644
--- a/services/java/com/android/server/location/PassiveProvider.java
+++ b/services/java/com/android/server/location/PassiveProvider.java
@@ -114,6 +114,6 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("mReportLocaiton=" + mReportLocation);
+        pw.println("mReportLocation=" + mReportLocation);
     }
 }