diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
| commit | 54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch) | |
| tree | 35051494d2af230dce54d6b31c6af8fc24091316 /location/java/com | |
| download | frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2 | |
Initial Contribution
Diffstat (limited to 'location/java/com')
44 files changed, 6346 insertions, 0 deletions
diff --git a/location/java/com/android/internal/location/CellState.java b/location/java/com/android/internal/location/CellState.java new file mode 100644 index 0000000..697fa92 --- /dev/null +++ b/location/java/com/android/internal/location/CellState.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location; + +import android.telephony.CellLocation; +import android.telephony.ServiceState; +import android.telephony.gsm.GsmCellLocation; +import com.android.internal.telephony.TelephonyProperties; + +import android.os.SystemProperties; + +/** + * Stores the cell tower state + * + * {@hide} + */ +public class CellState { + + public static String TAG = "CellState"; + + public static int RADIO_TYPE_GPRS = 1; + public static int RADIO_TYPE_CDMA = 2; + public static int RADIO_TYPE_WCDMA = 3; + + private int mCid = -1; + private int mLac = -1; + private int mMcc = -1; + private int mMnc = -1; + private int mHomeMcc = -1; + private int mHomeMnc = -1; + private String mCarrier = null; + private int mRadioType = -1; + private long mTime = 0; + + public CellState() { + // constructor for invalid cell location + } + + public CellState(ServiceState service, CellLocation location) { + GsmCellLocation loc = (GsmCellLocation)location; + mLac = loc.getLac(); // example: 6032 + mCid = loc.getCid(); // example: 31792 + mTime = System.currentTimeMillis(); + + // Get radio type + String radioType = SystemProperties.get(TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE); + if (radioType != null && (radioType.equals("GPRS") || radioType.equals("EDGE"))) { + mRadioType = RADIO_TYPE_GPRS; + } else if (radioType != null && radioType.equals("UMTS")) { + mRadioType = RADIO_TYPE_WCDMA; + } + + // Get MCC/MNC + String operator = service.getOperatorNumeric(); + if (operator != null && !operator.equals("")) { + String mcc = operator.substring(0, 3); + String mnc = operator.substring(3); + + mMcc = Integer.parseInt(mcc); + mMnc = Integer.parseInt(mnc); + } + + // Get Home MCC/MNC + String homeOperator = SystemProperties.get(TelephonyProperties.PROPERTY_SIM_OPERATOR_NUMERIC); + if (homeOperator != null && !homeOperator.equals("")) { + String mcc = homeOperator.substring(0, 3); + String mnc = homeOperator.substring(3); + + mHomeMcc = Integer.parseInt(mcc); + mHomeMnc = Integer.parseInt(mnc); + } + + // Get Carrier + String carrier = service.getOperatorAlphaLong(); + if (carrier != null && !carrier.equals("")) { + mCarrier = carrier; + } + + //Log.d(TAG, mLac +"," + mCid + "," + mMnc +"," + mMcc + "," + mHomeMcc + "," + + // mHomeMnc + "," + mCarrier + "," + mRadioType); + } + + public int getCid() { + return mCid; + } + + public int getLac() { + return mLac; + } + + public int getMcc() { + return mMcc; + } + + public int getMnc() { + return mMnc; + } + + public int getHomeMcc() { + return mHomeMcc; + } + + public void setHomeMcc(int homeMcc) { + this.mHomeMcc = homeMcc; + } + + public int getHomeMnc() { + return mHomeMnc; + } + + public void setHomeMnc(int homeMnc) { + this.mHomeMnc = homeMnc; + } + + public String getCarrier() { + return mCarrier; + } + + public void setCarrier(String carrier) { + this.mCarrier = carrier; + } + + public int getRadioType() { + return mRadioType; + } + + public long getTime() { + return mTime; + } + + public boolean equals(CellState other) { + return (mCid == other.mCid && mLac == other.mLac); + } + + public boolean isValid() { + return (mCid != -1 && mLac != -1); + } +} diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/location/java/com/android/internal/location/GpsLocationProvider.java new file mode 100644 index 0000000..d0e4f49 --- /dev/null +++ b/location/java/com/android/internal/location/GpsLocationProvider.java @@ -0,0 +1,879 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Properties; + +import android.content.Context; +import android.content.Intent; +import android.location.Criteria; +import android.location.IGpsStatusListener; +import android.location.Location; +import android.location.LocationManager; +import android.location.LocationProvider; +import android.location.LocationProviderImpl; +import android.net.SntpClient; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Config; +import android.util.Log; + +/** + * A GPS implementation of LocationProvider used by LocationManager. + * + * {@hide} + */ +public class GpsLocationProvider extends LocationProviderImpl { + + private static final String TAG = "GpsLocationProvider"; + + /** + * Broadcast intent action indicating that the GPS has either been + * enabled or disabled. An intent extra provides this state as a boolean, + * where {@code true} means enabled. + * @see #EXTRA_ENABLED + * + * {@hide} + */ + public static final String GPS_ENABLED_CHANGE_ACTION = + "android.location.GPS_ENABLED_CHANGE"; + + /** + * Broadcast intent action indicating that the GPS has either started or + * stopped receiving GPS fixes. An intent extra provides this state as a + * boolean, where {@code true} means that the GPS is actively receiving fixes. + * @see #EXTRA_ENABLED + * + * {@hide} + */ + public static final String GPS_FIX_CHANGE_ACTION = + "android.location.GPS_FIX_CHANGE"; + + /** + * The lookup key for a boolean that indicates whether GPS is enabled or + * disabled. {@code true} means GPS is enabled. Retrieve it with + * {@link android.content.Intent#getBooleanExtra(String,boolean)}. + * + * {@hide} + */ + public static final String EXTRA_ENABLED = "enabled"; + + // these need to match GpsStatusValue defines in gps.h + private static final int GPS_STATUS_NONE = 0; + private static final int GPS_STATUS_SESSION_BEGIN = 1; + private static final int GPS_STATUS_SESSION_END = 2; + private static final int GPS_STATUS_ENGINE_ON = 3; + private static final int GPS_STATUS_ENGINE_OFF = 4; + + // these need to match GpsLocationFlags enum in gps.h + private static final int LOCATION_INVALID = 0; + private static final int LOCATION_HAS_LAT_LONG = 1; + private static final int LOCATION_HAS_ALTITUDE = 2; + private static final int LOCATION_HAS_SPEED = 4; + private static final int LOCATION_HAS_BEARING = 8; + private static final int LOCATION_HAS_ACCURACY = 16; + +// IMPORTANT - the GPS_DELETE_* symbols here must match constants in GpsLocationProvider.java + private static final int GPS_DELETE_EPHEMERIS = 0x0001; + private static final int GPS_DELETE_ALMANAC = 0x0002; + private static final int GPS_DELETE_POSITION = 0x0004; + private static final int GPS_DELETE_TIME = 0x0008; + private static final int GPS_DELETE_IONO = 0x0010; + private static final int GPS_DELETE_UTC = 0x0020; + private static final int GPS_DELETE_HEALTH = 0x0040; + private static final int GPS_DELETE_SVDIR = 0x0080; + private static final int GPS_DELETE_SVSTEER = 0x0100; + private static final int GPS_DELETE_SADATA = 0x0200; + private static final int GPS_DELETE_RTI = 0x0400; + private static final int GPS_DELETE_CELLDB_INFO = 0x8000; + private static final int GPS_DELETE_ALL = 0xFFFF; + + private static final String PROPERTIES_FILE = "/etc/gps.conf"; + + private int mLocationFlags = LOCATION_INVALID; + + // current status + private int mStatus = TEMPORARILY_UNAVAILABLE; + + // time for last status update + private long mStatusUpdateTime = SystemClock.elapsedRealtime(); + + // turn off GPS fix icon if we haven't received a fix in 10 seconds + private static final long RECENT_FIX_TIMEOUT = 10 * 1000; + + // true if we are enabled + private boolean mEnabled; + // true if we are enabled for location updates + private boolean mLocationTracking; + + // true if we have network connectivity + private boolean mNetworkAvailable; + + // true if GPS is navigating + private boolean mNavigating; + + // requested frequency of fixes, in seconds + private int mFixInterval = 1; + + // true if we started navigation + private boolean mStarted; + + // for calculating time to first fix + private long mFixRequestTime = 0; + // time to first fix for most recent session + private int mTTFF = 0; + // time we received our last fix + private long mLastFixTime; + + // properties loaded from PROPERTIES_FILE + private Properties mProperties; + + private Context mContext; + private Location mLocation = new Location(LocationManager.GPS_PROVIDER); + private Bundle mLocationExtras = new Bundle(); + private ArrayList<Listener> mListeners = new ArrayList<Listener>(); + private GpsEventThread mEventThread; + private GpsNetworkThread mNetworkThread; + + // how often to request NTP time, in milliseconds + // current setting 4 hours + private static final long NTP_INTERVAL = 4*60*60*1000; + // how long to wait if we have a network error in NTP or XTRA downloading + // current setting - 5 minutes + private static final long RETRY_INTERVAL = 5*60*1000; + + private LocationCollector mCollector; + + public static boolean isSupported() { + return native_is_supported(); + } + + public GpsLocationProvider(Context context, LocationCollector collector) { + super(LocationManager.GPS_PROVIDER); + mContext = context; + mCollector = collector; + + mProperties = new Properties(); + try { + File file = new File(PROPERTIES_FILE); + FileInputStream stream = new FileInputStream(file); + mProperties.load(stream); + stream.close(); + } catch (IOException e) { + Log.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE, e); + } + } + + /** + * Returns true if the provider requires access to a + * data network (e.g., the Internet), false otherwise. + */ + @Override + public boolean requiresNetwork() { + // We want updateNetworkState() to get called when the network state changes + // for XTRA and NTP time injection support. + return true; + } + + public void updateNetworkState(int state) { + mNetworkAvailable = (state == LocationProvider.AVAILABLE); + + if (Config.LOGD) { + Log.d(TAG, "updateNetworkState " + (mNetworkAvailable ? "available" : "unavailable")); + } + + if (mNetworkAvailable && mNetworkThread != null && mEnabled) { + // signal the network thread when the network becomes available + mNetworkThread.signal(); + } + } + + /** + * Returns true if the provider requires access to a + * satellite-based positioning system (e.g., GPS), false + * otherwise. + */ + @Override + public boolean requiresSatellite() { + return true; + } + + /** + * Returns true if the provider requires access to an appropriate + * cellular network (e.g., to make use of cell tower IDs), false + * otherwise. + */ + @Override + public boolean requiresCell() { + return false; + } + + /** + * Returns true if the use of this provider may result in a + * monetary charge to the user, false if use is free. It is up to + * each provider to give accurate information. + */ + @Override + public boolean hasMonetaryCost() { + return false; + } + + /** + * Returns true if the provider is able to provide altitude + * information, false otherwise. A provider that reports altitude + * under most circumstances but may occassionally not report it + * should return true. + */ + @Override + public boolean supportsAltitude() { + return true; + } + + /** + * Returns true if the provider is able to provide speed + * information, false otherwise. A provider that reports speed + * under most circumstances but may occassionally not report it + * should return true. + */ + @Override + public boolean supportsSpeed() { + return true; + } + + /** + * Returns true if the provider is able to provide bearing + * information, false otherwise. A provider that reports bearing + * under most circumstances but may occassionally not report it + * should return true. + */ + @Override + public boolean supportsBearing() { + return true; + } + + /** + * Returns the power requirement for this provider. + * + * @return the power requirement for this provider, as one of the + * constants Criteria.POWER_REQUIREMENT_*. + */ + @Override + public int getPowerRequirement() { + return Criteria.POWER_HIGH; + } + + /** + * Returns the horizontal accuracy of this provider + * + * @return the accuracy of location from this provider, as one + * of the constants Criteria.ACCURACY_*. + */ + @Override + public int getAccuracy() { + return Criteria.ACCURACY_FINE; + } + + /** + * Enables this provider. When enabled, calls to getStatus() + * and getLocation() must be handled. Hardware may be started up + * when the provider is enabled. + */ + @Override + public void enable() { + if (Config.LOGD) Log.d(TAG, "enable"); + mEnabled = native_init(); + + if (mEnabled) { + // run event listener thread while we are enabled + mEventThread = new GpsEventThread(); + mEventThread.start(); + + // run network thread for NTP and XTRA support + if (mNetworkThread == null) { + mNetworkThread = new GpsNetworkThread(); + mNetworkThread.start(); + } else { + mNetworkThread.signal(); + } + } else { + Log.w(TAG, "Failed to enable location provider"); + } + } + + /** + * Disables this provider. When disabled, calls to getStatus() + * and getLocation() need not be handled. Hardware may be shut + * down while the provider is disabled. + */ + @Override + public void disable() { + if (Config.LOGD) Log.d(TAG, "disable"); + mEnabled = false; + stopNavigating(); + native_disable(); + + // make sure our event thread exits + if (mEventThread != null) { + try { + mEventThread.join(); + } catch (InterruptedException e) { + Log.w(TAG, "InterruptedException when joining mEventThread"); + } + mEventThread = null; + } + + native_cleanup(); + } + + @Override + public boolean isEnabled() { + return mEnabled; + } + + @Override + public int getStatus(Bundle extras) { + if (extras != null) { + extras.putInt("satellites", mSvCount); + } + return mStatus; + } + + private void updateStatus(int status, int svCount) { + if (status != mStatus || svCount != mSvCount) { + mStatus = status; + mSvCount = svCount; + mLocationExtras.putInt("satellites", svCount); + mStatusUpdateTime = SystemClock.elapsedRealtime(); + } + } + + @Override + public long getStatusUpdateTime() { + return mStatusUpdateTime; + } + + @Override + public boolean getLocation(Location l) { + synchronized (mLocation) { + // don't report locations without latitude and longitude + if ((mLocationFlags & LOCATION_HAS_LAT_LONG) == 0) { + return false; + } + l.set(mLocation); + l.setExtras(mLocationExtras); + return true; + } + } + + @Override + public void enableLocationTracking(boolean enable) { + if (mLocationTracking == enable) { + return; + } + + if (enable) { + mFixRequestTime = System.currentTimeMillis(); + mTTFF = 0; + mLastFixTime = 0; + startNavigating(); + } else { + stopNavigating(); + } + mLocationTracking = enable; + } + + @Override + public boolean isLocationTracking() { + return mLocationTracking; + } + + @Override + public void setMinTime(long minTime) { + super.setMinTime(minTime); + if (Config.LOGD) Log.d(TAG, "setMinTime " + minTime); + + if (minTime >= 0) { + int interval = (int)(minTime/1000); + if (interval < 1) { + interval = 1; + } + mFixInterval = interval; + native_set_fix_frequency(mFixInterval); + } + } + + private final class Listener implements IBinder.DeathRecipient { + final IGpsStatusListener mListener; + + int mSensors = 0; + + Listener(IGpsStatusListener listener) { + mListener = listener; + } + + public void binderDied() { + if (Config.LOGD) Log.d(TAG, "GPS status listener died"); + + synchronized(mListeners) { + mListeners.remove(this); + } + } + } + + public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException { + if (listener == null) throw new NullPointerException("listener is null in addGpsStatusListener"); + + synchronized(mListeners) { + IBinder binder = listener.asBinder(); + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener test = mListeners.get(i); + if (binder.equals(test.mListener.asBinder())) { + // listener already added + return; + } + } + + Listener l = new Listener(listener); + binder.linkToDeath(l, 0); + mListeners.add(l); + } + } + + public void removeGpsStatusListener(IGpsStatusListener listener) { + if (listener == null) throw new NullPointerException("listener is null in addGpsStatusListener"); + + synchronized(mListeners) { + IBinder binder = listener.asBinder(); + Listener l = null; + int size = mListeners.size(); + for (int i = 0; i < size && l == null; i++) { + Listener test = mListeners.get(i); + if (binder.equals(test.mListener.asBinder())) { + // listener already added + return; + } + } + + if (l != null) { + mListeners.remove(l); + binder.unlinkToDeath(l, 0); + } + } + } + + @Override + public boolean sendExtraCommand(String command, Bundle extras) { + + if ("delete_aiding_data".equals(command)) { + return deleteAidingData(extras); + } + + Log.w(TAG, "sendExtraCommand: unknown command " + command); + return false; + } + + private boolean deleteAidingData(Bundle extras) { + int flags; + + if (extras == null) { + flags = GPS_DELETE_ALL; + } else { + flags = 0; + if (extras.getBoolean("ephemeris")) flags |= GPS_DELETE_EPHEMERIS; + if (extras.getBoolean("almanac")) flags |= GPS_DELETE_ALMANAC; + if (extras.getBoolean("position")) flags |= GPS_DELETE_POSITION; + if (extras.getBoolean("time")) flags |= GPS_DELETE_TIME; + if (extras.getBoolean("iono")) flags |= GPS_DELETE_IONO; + if (extras.getBoolean("utc")) flags |= GPS_DELETE_UTC; + if (extras.getBoolean("health")) flags |= GPS_DELETE_HEALTH; + if (extras.getBoolean("svdir")) flags |= GPS_DELETE_SVDIR; + if (extras.getBoolean("svsteer")) flags |= GPS_DELETE_SVSTEER; + if (extras.getBoolean("sadata")) flags |= GPS_DELETE_SADATA; + if (extras.getBoolean("rti")) flags |= GPS_DELETE_RTI; + if (extras.getBoolean("celldb-info")) flags |= GPS_DELETE_CELLDB_INFO; + if (extras.getBoolean("all")) flags |= GPS_DELETE_ALL; + } + + if (flags != 0) { + native_delete_aiding_data(flags); + return true; + } + + return false; + } + + public void startNavigating() { + if (!mStarted) { + if (Config.LOGV) Log.v(TAG, "startNavigating"); + mStarted = true; + if (!native_start(false, mFixInterval)) { + mStarted = false; + Log.e(TAG, "native_start failed in startNavigating()"); + } + + // reset SV count to zero + updateStatus(TEMPORARILY_UNAVAILABLE, 0); + } + } + + public void stopNavigating() { + if (Config.LOGV) Log.v(TAG, "stopNavigating"); + if (mStarted) { + mStarted = false; + native_stop(); + mTTFF = 0; + mLastFixTime = 0; + mLocationFlags = LOCATION_INVALID; + + // reset SV count to zero + updateStatus(TEMPORARILY_UNAVAILABLE, 0); + } + } + + /** + * called from native code to update our position. + */ + private void reportLocation(int flags, double latitude, double longitude, double altitude, + float speed, float bearing, float accuracy, long timestamp) { + if (Config.LOGV) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude + + " timestamp: " + timestamp); + + mLastFixTime = System.currentTimeMillis(); + // report time to first fix + if (mTTFF == 0) { + mTTFF = (int)(mLastFixTime - mFixRequestTime); + if (Config.LOGD) Log.d(TAG, "TTFF: " + mTTFF); + + // notify status listeners + synchronized(mListeners) { + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + listener.mListener.onFirstFix(mTTFF); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in stopNavigating"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + } + } + + synchronized (mLocation) { + mLocationFlags = flags; + if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { + mLocation.setLatitude(latitude); + mLocation.setLongitude(longitude); + mLocation.setTime(timestamp); + } + if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) { + mLocation.setAltitude(altitude); + } else { + mLocation.removeAltitude(); + } + if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) { + mLocation.setSpeed(speed); + } else { + mLocation.removeSpeed(); + } + if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) { + mLocation.setBearing(bearing); + } else { + mLocation.removeBearing(); + } + if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) { + mLocation.setAccuracy(accuracy); + } else { + mLocation.removeAccuracy(); + } + + // Send to collector + if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { + mCollector.updateLocation(mLocation); + } + } + + if (mStarted && mStatus != AVAILABLE) { + // send an intent to notify that the GPS is receiving fixes. + Intent intent = new Intent(GPS_FIX_CHANGE_ACTION); + intent.putExtra(EXTRA_ENABLED, true); + mContext.sendBroadcast(intent); + updateStatus(AVAILABLE, mSvCount); + } + } + + /** + * called from native code to update our status + */ + private void reportStatus(int status) { + if (Config.LOGV) Log.v(TAG, "reportStatus status: " + status); + + boolean wasNavigating = mNavigating; + mNavigating = (status == GPS_STATUS_SESSION_BEGIN || status == GPS_STATUS_ENGINE_ON); + + if (wasNavigating != mNavigating) { + synchronized(mListeners) { + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + if (mNavigating) { + listener.mListener.onGpsStarted(); + } else { + listener.mListener.onGpsStopped(); + } + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportStatus"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + } + + // send an intent to notify that the GPS has been enabled or disabled. + Intent intent = new Intent(GPS_ENABLED_CHANGE_ACTION); + intent.putExtra(EXTRA_ENABLED, mNavigating); + mContext.sendBroadcast(intent); + } + } + + /** + * called from native code to update SV info + */ + private void reportSvStatus() { + + int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks); + + synchronized(mListeners) { + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs, + mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK], + mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportSvInfo"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + } + + if (Config.LOGD) { + if (Config.LOGV) Log.v(TAG, "SV count: " + svCount + + " ephemerisMask: " + Integer.toHexString(mSvMasks[EPHEMERIS_MASK]) + + " almanacMask: " + Integer.toHexString(mSvMasks[ALMANAC_MASK])); + for (int i = 0; i < svCount; i++) { + if (Config.LOGV) Log.v(TAG, "sv: " + mSvs[i] + + " snr: " + (float)mSnrs[i]/10 + + " elev: " + mSvElevations[i] + + " azimuth: " + mSvAzimuths[i] + + ((mSvMasks[EPHEMERIS_MASK] & (1 << (mSvs[i] - 1))) == 0 ? " " : " E") + + ((mSvMasks[ALMANAC_MASK] & (1 << (mSvs[i] - 1))) == 0 ? " " : " A") + + ((mSvMasks[USED_FOR_FIX_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "" : "U")); + } + } + + updateStatus(mStatus, svCount); + + if (mNavigating && mStatus == AVAILABLE && mLastFixTime > 0 && + System.currentTimeMillis() - mLastFixTime > RECENT_FIX_TIMEOUT) { + // send an intent to notify that the GPS is no longer receiving fixes. + Intent intent = new Intent(GPS_FIX_CHANGE_ACTION); + intent.putExtra(EXTRA_ENABLED, false); + mContext.sendBroadcast(intent); + updateStatus(TEMPORARILY_UNAVAILABLE, mSvCount); + } + } + + private void xtraDownloadRequest() { + if (Config.LOGD) Log.d(TAG, "xtraDownloadRequest"); + if (mNetworkThread != null) { + mNetworkThread.xtraDownloadRequest(); + } + } + + private class GpsEventThread extends Thread { + + public GpsEventThread() { + super("GpsEventThread"); + } + + public void run() { + if (Config.LOGD) Log.d(TAG, "GpsEventThread starting"); + // thread exits after disable() is called and navigation has stopped + while (mEnabled || mNavigating) { + // this will wait for an event from the GPS, + // which will be reported via reportLocation or reportStatus + native_wait_for_event(); + } + if (Config.LOGD) Log.d(TAG, "GpsEventThread exiting"); + } + } + + private class GpsNetworkThread extends Thread { + + private long mNextNtpTime = 0; + private long mNextXtraTime = 0; + private boolean mXtraDownloadRequested = false; + + public GpsNetworkThread() { + super("GpsNetworkThread"); + } + + public void run() { + if (Config.LOGD) Log.d(TAG, "NetworkThread starting"); + + SntpClient client = new SntpClient(); + GpsXtraDownloader xtraDownloader = null; + + if (native_supports_xtra()) { + xtraDownloader = new GpsXtraDownloader(mContext, mProperties); + } + + // thread exits after disable() is called + while (mEnabled) { + long waitTime = getWaitTime(); + do { + synchronized (this) { + try { + if (!mNetworkAvailable) { + if (Config.LOGD) Log.d(TAG, "NetworkThread wait for network"); + wait(); + } else if (waitTime > 0) { + if (Config.LOGD) Log.d(TAG, "NetworkThread wait for " + waitTime + "ms"); + wait(waitTime); + } + } catch (InterruptedException e) { + if (Config.LOGD) Log.d(TAG, "InterruptedException in GpsNetworkThread"); + } + } + waitTime = getWaitTime(); + } while (mEnabled && ((!mXtraDownloadRequested && waitTime > 0) || !mNetworkAvailable)); + if (Config.LOGD) Log.d(TAG, "NetworkThread out of wake loop"); + + if (mEnabled) { + if (mNextNtpTime <= System.currentTimeMillis()) { + String ntpServer = mProperties.getProperty("NTP_SERVER", "pool.ntp.org"); + if (Config.LOGD) Log.d(TAG, "Requesting time from NTP server " + ntpServer); + if (client.requestTime(ntpServer, 10000)) { + long time = client.getNtpTime(); + long timeReference = client.getNtpTimeReference(); + int certainty = (int)(client.getRoundTripTime()/2); + + if (Config.LOGD) Log.d(TAG, "calling native_inject_time: " + + time + " reference: " + timeReference + + " certainty: " + certainty); + + native_inject_time(time, timeReference, certainty); + mNextNtpTime = System.currentTimeMillis() + NTP_INTERVAL; + } else { + if (Config.LOGD) Log.d(TAG, "requestTime failed"); + mNextNtpTime = System.currentTimeMillis() + RETRY_INTERVAL; + } + } + + if ((mXtraDownloadRequested || + (mNextXtraTime > 0 && mNextXtraTime <= System.currentTimeMillis())) && + xtraDownloader != null) { + byte[] data = xtraDownloader.downloadXtraData(); + if (data != null) { + if (Config.LOGD) Log.d(TAG, "calling native_inject_xtra_data"); + native_inject_xtra_data(data, data.length); + mNextXtraTime = 0; + mXtraDownloadRequested = false; + } else { + mNextXtraTime = System.currentTimeMillis() + RETRY_INTERVAL; + } + } + } + } + if (Config.LOGD) Log.d(TAG, "NetworkThread exiting"); + } + + synchronized void xtraDownloadRequest() { + mXtraDownloadRequested = true; + notify(); + } + + synchronized void signal() { + notify(); + } + + private long getWaitTime() { + long now = System.currentTimeMillis(); + long waitTime = mNextNtpTime - now; + if (mNextXtraTime != 0) { + long xtraWaitTime = mNextXtraTime - now; + if (xtraWaitTime < waitTime) { + waitTime = xtraWaitTime; + } + } + if (waitTime < 0) { + waitTime = 0; + } + return waitTime; + } + } + + // for GPS SV statistics + private static final int MAX_SVS = 32; + private static final int EPHEMERIS_MASK = 0; + private static final int ALMANAC_MASK = 1; + private static final int USED_FOR_FIX_MASK = 2; + + // preallocated arrays, to avoid memory allocation in reportStatus() + private int mSvs[] = new int[MAX_SVS]; + private float mSnrs[] = new float[MAX_SVS]; + private float mSvElevations[] = new float[MAX_SVS]; + private float mSvAzimuths[] = new float[MAX_SVS]; + private int mSvMasks[] = new int[3]; + private int mSvCount; + + static { class_init_native(); } + private static native void class_init_native(); + private static native boolean native_is_supported(); + + private native boolean native_init(); + private native void native_disable(); + private native void native_cleanup(); + private native boolean native_start(boolean singleFix, int fixInterval); + private native boolean native_stop(); + private native void native_set_fix_frequency(int fixFrequency); + private native void native_delete_aiding_data(int flags); + private native void native_wait_for_event(); + // returns number of SVs + // mask[0] is ephemeris mask and mask[1] is almanac mask + private native int native_read_sv_status(int[] svs, float[] snrs, + float[] elevations, float[] azimuths, int[] masks); + + private native void native_inject_time(long time, long timeReference, int uncertainty); + private native boolean native_supports_xtra(); + private native void native_inject_xtra_data(byte[] data, int length); +} diff --git a/location/java/com/android/internal/location/GpsXtraDownloader.java b/location/java/com/android/internal/location/GpsXtraDownloader.java new file mode 100644 index 0000000..6efbbf6 --- /dev/null +++ b/location/java/com/android/internal/location/GpsXtraDownloader.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.params.ConnRouteParams; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Properties; + +import android.content.Context; +import android.net.Proxy; +import android.net.http.AndroidHttpClient; +import android.util.Log; + +/** + * A class for downloading GPS XTRA data. + * + * {@hide} + */ +public class GpsXtraDownloader { + + private static final String TAG = "GpsXtraDownloader"; + + private Context mContext; + private String[] mXtraServers; + // to load balance our server requests + private int mNextServerIndex = 0; + + GpsXtraDownloader(Context context, Properties properties) { + mContext = context; + + // read XTRA servers from the Properties object + int count = 0; + String server1 = properties.getProperty("XTRA_SERVER_1"); + String server2 = properties.getProperty("XTRA_SERVER_2"); + String server3 = properties.getProperty("XTRA_SERVER_3"); + if (server1 != null) count++; + if (server2 != null) count++; + if (server3 != null) count++; + + if (count == 0) { + Log.e(TAG, "No XTRA servers were specified in the GPS configuration"); + } else { + mXtraServers = new String[count]; + count = 0; + if (server1 != null) mXtraServers[count++] = server1; + if (server2 != null) mXtraServers[count++] = server2; + if (server3 != null) mXtraServers[count++] = server3; + } + } + + byte[] downloadXtraData() { + String proxyHost = Proxy.getHost(mContext); + int proxyPort = Proxy.getPort(mContext); + boolean useProxy = (proxyHost != null && proxyPort != -1); + byte[] result = null; + int startIndex = mNextServerIndex; + + if (mXtraServers == null) { + return null; + } + + // load balance our requests among the available servers + while (result == null) { + result = doDownload(mXtraServers[mNextServerIndex], useProxy, proxyHost, proxyPort); + + // increment mNextServerIndex and wrap around if necessary + mNextServerIndex++; + if (mNextServerIndex == mXtraServers.length) { + mNextServerIndex = 0; + } + // break if we have tried all the servers + if (mNextServerIndex == startIndex) break; + } + + return result; + } + + protected static byte[] doDownload(String url, boolean isProxySet, + String proxyHost, int proxyPort) { + AndroidHttpClient client = null; + try { + client = AndroidHttpClient.newInstance("Android"); + HttpUriRequest req = new HttpGet(url); + + if (isProxySet) { + HttpHost proxy = new HttpHost(proxyHost, proxyPort); + ConnRouteParams.setDefaultProxy(req.getParams(), proxy); + } + + req.addHeader( + "Accept", + "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic"); + + req.addHeader( + "x-wap-profile", + "http://www.openmobilealliance.org/tech/profiles/UAPROF/ccppschema-20021212#"); + + HttpResponse response = client.execute(req); + StatusLine status = response.getStatusLine(); + if (status.getStatusCode() != 200) { // HTTP 200 is success. + Log.d(TAG, "HTTP error: " + status.getReasonPhrase()); + return null; + } + + HttpEntity entity = response.getEntity(); + byte[] body = null; + if (entity != null) { + try { + if (entity.getContentLength() > 0) { + body = new byte[(int) entity.getContentLength()]; + DataInputStream dis = new DataInputStream(entity.getContent()); + try { + dis.readFully(body); + } finally { + try { + dis.close(); + } catch (IOException e) { + Log.e(TAG, "Unexpected IOException.", e); + } + } + } + } finally { + if (entity != null) { + entity.consumeContent(); + } + } + } + return body; + } catch (Exception e) { + Log.d(TAG, "error " + e); + } finally { + if (client != null) { + client.close(); + } + } + return null; + } + +} + diff --git a/location/java/com/android/internal/location/LocationCache.java b/location/java/com/android/internal/location/LocationCache.java new file mode 100644 index 0000000..f0928f9 --- /dev/null +++ b/location/java/com/android/internal/location/LocationCache.java @@ -0,0 +1,577 @@ +// Copyright 2007 The Android Open Source Project + +package com.android.internal.location; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import android.location.Location; +import android.location.LocationManager; +import android.net.wifi.ScanResult; +import android.os.Bundle; +import android.util.Log; + +/** + * Data store to cache cell-id and wifi locations from the network + * + * {@hide} + */ +public class LocationCache { + private static final String TAG = "LocationCache"; + + // Version of cell cache + private static final int CACHE_DB_VERSION = 1; + + // Don't save cache more than once every minute + private static final long SAVE_FREQUENCY = 60 * 1000; + + // Location of the cache file; + private static final String mCellCacheFile = "cache.cell"; + private static final String mWifiCacheFile = "cache.wifi"; + + // Maximum time (in millis) that a record is valid for, before it needs + // to be refreshed from the server. + private static final long MAX_CELL_REFRESH_RECORD_AGE = 12 * 60 * 60 * 1000; // 12 hours + private static final long MAX_WIFI_REFRESH_RECORD_AGE = 48 * 60 * 60 * 1000; // 48 hours + + // Cache sizes + private static final int MAX_CELL_RECORDS = 50; + private static final int MAX_WIFI_RECORDS = 200; + + // Cache constants + private static final long CELL_SMOOTHING_WINDOW = 30 * 1000; // 30 seconds + private static final int WIFI_MIN_AP_REQUIRED = 2; + private static final int WIFI_MAX_MISS_ALLOWED = 5; + private static final int MAX_ACCURACY_ALLOWED = 5000; // 5km + + // Caches + private final Cache<Record> mCellCache; + private final Cache<Record> mWifiCache; + + // Currently calculated centroids + private final LocationCentroid mCellCentroid = new LocationCentroid(); + private final LocationCentroid mWifiCentroid = new LocationCentroid(); + + // Extra key and values + private final String EXTRA_KEY_LOCATION_TYPE = "networkLocationType"; + private final String EXTRA_VALUE_LOCATION_TYPE_CELL = "cell"; + private final String EXTRA_VALUE_LOCATION_TYPE_WIFI = "wifi"; + + public LocationCache() { + mCellCache = new Cache<Record>(LocationManager.SYSTEM_DIR, mCellCacheFile, + MAX_CELL_RECORDS, MAX_CELL_REFRESH_RECORD_AGE); + mWifiCache = new Cache<Record>(LocationManager.SYSTEM_DIR, mWifiCacheFile, + MAX_WIFI_RECORDS, MAX_WIFI_REFRESH_RECORD_AGE); + } + + /** + * Looks up network location on device cache + * + * @param cellState primary cell state + * @param cellHistory history of cell states + * @param scanResults wifi scan results + * @param result location object to fill if location is found + * @return true if cache was able to answer query (successfully or not), false if call to + * server is required + */ + public synchronized boolean lookup(CellState cellState, List<CellState> cellHistory, + List<ScanResult> scanResults, Location result) { + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "including cell:" + (cellState != null) + + ", wifi:" + ((scanResults != null)? scanResults.size() : "null")); + } + + long now = System.currentTimeMillis(); + + mCellCentroid.reset(); + mWifiCentroid.reset(); + + if (cellState != null) { + String primaryCellKey = getCellCacheKey(cellState.getMcc(), cellState.getMnc(), + cellState.getLac(), cellState.getCid()); + Record record = mCellCache.lookup(primaryCellKey); + + if (record == null) { + // Make a server request if primary cell doesn't exist in DB + return false; + } + + if (record.isValid()) { + mCellCentroid.addLocation(record.getLat(), record.getLng(), record.getAccuracy(), + record.getConfidence()); + } + } + + if (cellHistory != null) { + for (CellState historicalCell : cellHistory) { + // Cell location might need to be smoothed if you are on the border of two cells + if (now - historicalCell.getTime() < CELL_SMOOTHING_WINDOW) { + String historicalCellKey = getCellCacheKey(historicalCell.getMcc(), + historicalCell.getMnc(), historicalCell.getLac(), historicalCell.getCid()); + Record record = mCellCache.lookup(historicalCellKey); + if (record != null && record.isValid()) { + mCellCentroid.addLocation(record.getLat(), record.getLng(), + record.getAccuracy(), record.getConfidence()); + } + } + } + } + + if (scanResults != null) { + int miss = 0; + for (ScanResult scanResult : scanResults) { + String wifiKey = scanResult.BSSID; + Record record = mWifiCache.lookup(wifiKey); + if (record == null) { + miss++; + } else { + if (record.isValid()) { + mWifiCentroid.addLocation(record.getLat(), record.getLng(), + record.getAccuracy(), record.getConfidence()); + } + } + } + + if (mWifiCentroid.getNumber() >= WIFI_MIN_AP_REQUIRED) { + // Try to return best out of the available cell or wifi location + } else if (miss > Math.min(WIFI_MAX_MISS_ALLOWED, (scanResults.size()+1)/2)) { + // Make a server request + return false; + } else { + // Don't use wifi location, only consider using cell location + mWifiCache.save(); + mWifiCentroid.reset(); + } + } + + if (mCellCentroid.getNumber() > 0) { + mCellCache.save(); + } + if (mWifiCentroid.getNumber() > 0) { + mWifiCache.save(); + } + + int cellAccuracy = mCellCentroid.getAccuracy(); + int wifiAccuracy = mWifiCentroid.getAccuracy(); + + int cellConfidence = mCellCentroid.getConfidence(); + int wifiConfidence = mWifiCentroid.getConfidence(); + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "cellAccuracy:" + cellAccuracy+ ", wifiAccuracy:" + wifiAccuracy); + } + + if (mCellCentroid.getNumber() != 0 && cellAccuracy <= MAX_ACCURACY_ALLOWED && + (mWifiCentroid.getNumber() == 0 || cellConfidence >= wifiConfidence || + cellAccuracy < wifiAccuracy)) { + // Use cell results + result.setAccuracy(cellAccuracy); + result.setLatitude(mCellCentroid.getCentroidLat()); + result.setLongitude(mCellCentroid.getCentroidLng()); + result.setTime(now); + + Bundle extras = result.getExtras() == null ? new Bundle() : result.getExtras(); + extras.putString(EXTRA_KEY_LOCATION_TYPE, EXTRA_VALUE_LOCATION_TYPE_CELL); + result.setExtras(extras); + + } else if (mWifiCentroid.getNumber() != 0 && wifiAccuracy <= MAX_ACCURACY_ALLOWED) { + // Use wifi results + result.setAccuracy(wifiAccuracy); + result.setLatitude(mWifiCentroid.getCentroidLat()); + result.setLongitude(mWifiCentroid.getCentroidLng()); + result.setTime(now); + + Bundle extras = result.getExtras() == null ? new Bundle() : result.getExtras(); + extras.putString(EXTRA_KEY_LOCATION_TYPE, EXTRA_VALUE_LOCATION_TYPE_WIFI); + result.setExtras(extras); + + } else { + // Return invalid location + result.setAccuracy(-1); + } + + // don't make a server request + return true; + } + + public synchronized void insert(int mcc, int mnc, int lac, int cid, double lat, double lng, + int accuracy, int confidence, long time) { + String key = getCellCacheKey(mcc, mnc, lac, cid); + if (accuracy <= 0) { + mCellCache.insert(key, new Record()); + } else { + mCellCache.insert(key, new Record(accuracy, confidence, lat, lng, time)); + } + } + + public synchronized void insert(String bssid, double lat, double lng, int accuracy, + int confidence, long time) { + if (accuracy <= 0) { + mWifiCache.insert(bssid, new Record()); + } else { + mWifiCache.insert(bssid, new Record(accuracy, confidence, lat, lng, time)); + } + } + + public synchronized void save() { + mCellCache.save(); + mWifiCache.save(); + } + + /** + * Cell or Wifi location record + */ + public static class Record { + + private final double lat; + private final double lng; + private final int accuracy; + private final int confidence; + + // Time (since the epoch) of original reading. + private final long originTime; + + public static Record read(DataInput dataInput) throws IOException { + final int accuracy = dataInput.readInt(); + final int confidence = dataInput.readInt(); + final double lat = dataInput.readDouble(); + final double lng = dataInput.readDouble(); + final long readingTime = dataInput.readLong(); + return new Record(accuracy, confidence, lat, lng, readingTime); + } + + /** + * Creates an "invalid" record indicating there was no location data + * available for the given data + */ + public Record() { + this(-1, 0, 0, 0, System.currentTimeMillis()); + } + + /** + * Creates a Record + * + * @param accuracy acuracy in meters. If < 0, then this is an invalid record. + * @param confidence confidence (0-100) + * @param lat latitude + * @param lng longitude + * @param time Time of the original location reading from the server + */ + public Record(int accuracy, int confidence, double lat, double lng, long time) { + this.accuracy = accuracy; + this.confidence = confidence; + this.originTime = time; + this.lat = lat; + this.lng = lng; + } + + public double getLat() { + return lat; + } + + public double getLng() { + return lng; + } + + public int getAccuracy() { + return accuracy; + } + + public int getConfidence() { + return confidence; + } + + public boolean isValid() { + return accuracy > 0; + } + + public long getTime() { + return originTime; + } + + public void write(DataOutput dataOut) throws IOException { + dataOut.writeInt(accuracy); + dataOut.writeInt(confidence); + dataOut.writeDouble(lat); + dataOut.writeDouble(lng); + dataOut.writeLong(originTime); + } + + @Override + public String toString() { + return lat + "," + lng + "," + originTime +"," + accuracy + "," + confidence; + } + } + + public class Cache<T> extends LinkedHashMap { + private final long mMaxAge; + private final int mCapacity; + private final String mDir; + private final String mFile; + private long mLastSaveTime = 0; + + public Cache(String dir, String file, int capacity, long maxAge) { + super(capacity + 1, 1.1f, true); + this.mCapacity = capacity; + this.mDir = dir; + this.mFile = file; + this.mMaxAge = maxAge; + load(); + } + + private LocationCache.Record lookup(String key) { + LocationCache.Record result = (LocationCache.Record) get(key); + + if (result == null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "lookup: " + key + " failed"); + } + return null; + } + + // Cache entry needs refresh + if (result.getTime() + mMaxAge < System.currentTimeMillis()) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "lookup: " + key + " expired"); + } + return null; + } + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "lookup: " + key + " " + result.toString()); + } + + return result; + } + + private void insert(String key, LocationCache.Record record) { + remove(key); + put(key, record); + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "insert: " + key + " " + record.toString()); + } + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + // Remove cache entries when it has more than capacity + return size() > mCapacity; + } + + private void load() { + FileInputStream istream; + try { + File f = new File(mDir, mFile); + istream = new FileInputStream(f); + } catch (FileNotFoundException e) { + // No existing DB - return new CellCache + return; + } + + DataInputStream dataInput = new DataInputStream(istream); + + try { + int version = dataInput.readUnsignedShort(); + if (version != CACHE_DB_VERSION) { + // Ignore records - invalid version ID. + dataInput.close(); + return; + } + int records = dataInput.readUnsignedShort(); + + for (int i = 0; i < records; i++) { + final String key = dataInput.readUTF(); + final LocationCache.Record record = LocationCache.Record.read(dataInput); + //Log.d(TAG, key + " " + record.toString()); + put(key, record); + } + + dataInput.close(); + } catch (IOException e) { + // Something's corrupted - return a new CellCache + } + } + + private void save() { + long now = System.currentTimeMillis(); + if (mLastSaveTime != 0 && (now - mLastSaveTime < SAVE_FREQUENCY)) { + // Don't save to file more often than SAVE_FREQUENCY + return; + } + + FileOutputStream ostream; + + File systemDir = new File(mDir); + if (!systemDir.exists()) { + if (!systemDir.mkdirs()) { + Log.e(TAG, "Cache.save(): couldn't create directory"); + return; + } + } + + try { + File f = new File(mDir, mFile); + ostream = new FileOutputStream(f); + } catch (FileNotFoundException e) { + Log.d(TAG, "Cache.save(): unable to create cache file", e); + return; + } + + DataOutputStream dataOut = new DataOutputStream(ostream); + try { + dataOut.writeShort(CACHE_DB_VERSION); + + dataOut.writeShort(size()); + + for (Iterator iter = entrySet().iterator(); iter.hasNext();) { + Map.Entry entry = (Map.Entry) iter.next(); + String key = (String) entry.getKey(); + LocationCache.Record record = (LocationCache.Record) entry.getValue(); + dataOut.writeUTF(key); + record.write(dataOut); + } + + dataOut.close(); + mLastSaveTime = now; + + } catch (IOException e) { + Log.e(TAG, "Cache.save(): unable to write cache", e); + // This should never happen + } + } + } + + public class LocationCentroid { + + double mLatSum = 0; + double mLngSum = 0; + int mNumber = 0; + int mConfidenceSum = 0; + + double mCentroidLat = 0; + double mCentroidLng = 0; + + // Probably never have to calculate centroid for more than 10 locations + final static int MAX_SIZE = 10; + double[] mLats = new double[MAX_SIZE]; + double[] mLngs = new double[MAX_SIZE]; + int[] mRadii = new int[MAX_SIZE]; + + LocationCentroid() { + reset(); + } + + public void reset() { + mLatSum = 0; + mLngSum = 0; + mNumber = 0; + mConfidenceSum = 0; + + mCentroidLat = 0; + mCentroidLng = 0; + + for (int i = 0; i < MAX_SIZE; i++) { + mLats[i] = 0; + mLngs[i] = 0; + mRadii[i] = 0; + } + } + + public void addLocation(double lat, double lng, int accuracy, int confidence) { + if (mNumber < MAX_SIZE && accuracy <= MAX_ACCURACY_ALLOWED) { + mLatSum += lat; + mLngSum += lng; + mConfidenceSum += confidence; + + mLats[mNumber] = lat; + mLngs[mNumber] = lng; + mRadii[mNumber] = accuracy; + mNumber++; + } + } + + public int getNumber() { + return mNumber; + } + + public double getCentroidLat() { + if (mCentroidLat == 0 && mNumber != 0) { + mCentroidLat = mLatSum/mNumber; + } + return mCentroidLat; + } + + public double getCentroidLng() { + if (mCentroidLng == 0 && mNumber != 0) { + mCentroidLng = mLngSum/mNumber; + } + return mCentroidLng; + } + + public int getConfidence() { + if (mNumber != 0) { + return mConfidenceSum/mNumber; + } else { + return 0; + } + } + + public int getAccuracy() { + if (mNumber == 0) { + return 0; + } + + if (mNumber == 1) { + return mRadii[0]; + } + + double cLat = getCentroidLat(); + double cLng = getCentroidLng(); + + int meanDistanceSum = 0; + int meanRadiiSum = 0; + int smallestCircle = MAX_ACCURACY_ALLOWED; + int smallestCircleDistance = MAX_ACCURACY_ALLOWED; + float[] distance = new float[1]; + boolean outlierExists = false; + + for (int i = 0; i < mNumber; i++) { + Location.distanceBetween(cLat, cLng, mLats[i], mLngs[i], distance); + meanDistanceSum += (int)distance[0]; + if (distance[0] > mRadii[i]) { + outlierExists = true; + } + if (mRadii[i] < smallestCircle) { + smallestCircle = mRadii[i]; + smallestCircleDistance = (int)distance[0]; + } + meanRadiiSum += mRadii[i]; + } + + if (outlierExists) { + return (meanDistanceSum + meanRadiiSum)/mNumber; + } else { + return Math.max(smallestCircle, smallestCircleDistance); + } + } + + } + + private String getCellCacheKey(int mcc, int mnc, int lac, int cid) { + return mcc + ":" + mnc + ":" + lac + ":" + cid; + } + +} diff --git a/location/java/com/android/internal/location/LocationCollector.java b/location/java/com/android/internal/location/LocationCollector.java new file mode 100644 index 0000000..3d2f6bf --- /dev/null +++ b/location/java/com/android/internal/location/LocationCollector.java @@ -0,0 +1,499 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location; + +import com.android.internal.location.protocol.GDebugProfile; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import android.location.Location; +import android.net.wifi.ScanResult; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.util.Log; + +/** + * Listens for GPS and cell/wifi changes and anonymously uploads to server for + * improving quality of service of NetworkLocationProvider. This service is only enabled when + * the user has enabled the network location provider. + * + * {@hide} + */ +public class LocationCollector { + + private static final String TAG = "LocationCollector"; + + // last location valid for 12 minutes + private static final long MIN_VALID_LOCATION_TIME = 12 * 60 * 1000L; + + // don't send wifi more than every 10 min + private static final long MIN_TIME_BETWEEN_WIFI_REPORTS = 10 * 60 * 1000L; + + // atleast 5 changed APs for wifi collection + private static final int MIN_CHANGED_WIFI_POINTS = 5; + + // don't collect if distance moved less than 200 meters + private static final int MIN_DISTANCE_BETWEEN_REPORTS = 200; + + // don't collect if battery level less than 20% + private static final double MIN_BATTERY_LEVEL = 0.2; + + // if battery level is greater than 90% and plugged in, collect more frequently + private static final double CHARGED_BATTERY_LEVEL = 0.9; + + // collect bursts every 15 minutes (running on battery) + private static final long BURST_REST_TIME_ON_BATTERY = 15 * 60 * 1000L; + + // collect bursts every 8 minutes (when plugged in) + private static final long BURST_REST_TIME_PLUGGED = 8 * 60 * 1000L; + + // collect burst samples every 12 seconds + private static final int BURST_MEASUREMENT_INTERVAL = 12 * 1000; + + // collect 11 burst samples before resting (11 samples every 12 seconds = 2 minute bursts) + private static final int BURST_NUM_SAMPLES = 11; + + // don't collect bursts if user in same loc for 2 bursts + private static final int MAX_BURSTS_FROM_SAME_LOCATION = 2; + + // don't send more than 2 bursts if user hasn't moved more than 25 meters + private static final int MIN_DISTANCE_BETWEEN_BURSTS = 25; + + // Cell State + private CellState mCellState = null; + private CellUploads mCellUploads = new CellUploads(); + + // GPS state + private Location mLastKnownLocation = null; + private Location mLastUploadedLocation = null; + private long mLastKnownLocationTime = 0; + private long mLastUploadedLocationTime = 0; + + // Burst state + private Location mLastBurstLocation = null; + private long mLastBurstEndTime = 0; + private long mCurrentBurstStartTime = 0; + private int mCurrentBurstNumSamples = 0; + private int mNumBurstsFromLastLocation = 0; + + // WiFi state + private List<ScanResult> mWifiLastScanResults = null; + private List<ScanResult> mWifiCurrentScanResults = null; + private long mLastWifiScanElapsedTime = 0; + private long mLastWifiScanRealTime = 0; + private boolean mWifiUploadedWithoutLocation = false; + + // Collection state + private boolean mNetworkProviderIsEnabled = true; + private boolean mBatteryLevelIsHealthy = true; + private boolean mBatteryChargedAndPlugged = false; + + // Location masf service + private LocationMasfClient mMasfClient; + + public LocationCollector(LocationMasfClient masfClient) { + mMasfClient = masfClient; + } + + /** + * Updates cell tower state. This is usually always up to date so should be uploaded + * each time a new location is available. + * + * @param newState cell state + */ + public synchronized void updateCellState(CellState newState) { + if (newState == null) { + throw new IllegalArgumentException("cell state is null"); + } + + if (!newState.isValid()) { + return; + } + + if (mCellState != null && mCellState.equals(newState)) { + return; + } + + mCellState = newState; + log("updateCellState(): Updated to " + mCellState.getCid() + "," + mCellState.getLac()); + + if (isCollectionEnabled()) { + addToQueue(GDebugProfile.TRIGGER_CELL_CHANGE); + } + } + + /** + * Updates GPS location if collection is enabled + * + * @param location location object + */ + public synchronized void updateLocation(Location location) { + + // Don't do anything if collection is disabled + if (!isCollectionEnabled()) { + return; + } + + long now = SystemClock.elapsedRealtime(); + + // Update last known location + if (mLastKnownLocation == null) { + mLastKnownLocation = new Location(location); + } else { + mLastKnownLocation.set(location); + } + mLastKnownLocationTime = now; + + // Burst rest time depends on battery state + long restTime = BURST_REST_TIME_ON_BATTERY; + if (mBatteryChargedAndPlugged) { + restTime = BURST_REST_TIME_PLUGGED; + } + + int trigger; + + // In burst mode if either first burst or enough time has passed since last burst + if (mLastBurstEndTime == 0 || (now - mLastBurstEndTime > restTime)) { + + // If location is too recent, then don't do anything! + if (now - mLastUploadedLocationTime < BURST_MEASUREMENT_INTERVAL) { + return; + } + + int distanceFromLastBurst = -1; + if (mLastBurstLocation != null) { + distanceFromLastBurst = (int) mLastBurstLocation.distanceTo(location); + + // Too many bursts from same location, don't upload + if (distanceFromLastBurst < MIN_DISTANCE_BETWEEN_BURSTS && + mNumBurstsFromLastLocation >= MAX_BURSTS_FROM_SAME_LOCATION) { + log("NO UPLOAD: Too many bursts from same location."); + return; + } + } + + if (mCurrentBurstStartTime == 0) { + // Start the burst! + mCurrentBurstStartTime = now; + mCurrentBurstNumSamples = 1; + trigger = GDebugProfile.TRIGGER_COLLECTION_START_BURST; + + } else if (now - mCurrentBurstStartTime > restTime) { + // Burst got old, start a new one + mCurrentBurstStartTime = now; + mCurrentBurstNumSamples = 1; + trigger = GDebugProfile.TRIGGER_COLLECTION_RESTART_BURST; + + } else if (mCurrentBurstNumSamples == BURST_NUM_SAMPLES - 1) { + // Finished a burst + mLastBurstEndTime = now; + mCurrentBurstStartTime = 0; + mCurrentBurstNumSamples = 0; + + // Make sure we don't upload too many bursts from same location + if (mLastBurstLocation == null) { + mLastBurstLocation = new Location(location); + mNumBurstsFromLastLocation = 1; + trigger = GDebugProfile.TRIGGER_COLLECTION_END_BURST; + + } else { + + if (distanceFromLastBurst != -1 && + distanceFromLastBurst < MIN_DISTANCE_BETWEEN_BURSTS) { + // User hasnt moved much from last location, keep track of count, + // don't update last burst loc + mNumBurstsFromLastLocation++; + trigger = GDebugProfile.TRIGGER_COLLECTION_END_BURST_AT_SAME_LOCATION; + + } else { + // User has moved enough, update last burst loc + mLastBurstLocation.set(location); + mNumBurstsFromLastLocation = 1; + trigger = GDebugProfile.TRIGGER_COLLECTION_END_BURST; + } + } + + } else { + // Increment burst sample count + mCurrentBurstNumSamples++; + trigger = GDebugProfile.TRIGGER_COLLECTION_CONTINUE_BURST; + } + + } else if (mLastUploadedLocation != null + && (mLastUploadedLocation.distanceTo(location) > MIN_DISTANCE_BETWEEN_REPORTS)) { + // If not in burst mode but has moved a reasonable distance, upload! + trigger = GDebugProfile.TRIGGER_COLLECTION_MOVED_DISTANCE; + + } else { + // Not in burst mode or hasn't moved enough + log("NO UPLOAD: Not in burst or moving mode. Resting for " + restTime + " ms"); + return; + } + + log("updateLocation(): Updated location with trigger " + trigger); + addToQueue(trigger); + } + + /** + * Updates wifi scan results if collection is enabled + * + * @param currentScanResults scan results + */ + public synchronized void updateWifiScanResults(List<ScanResult> currentScanResults) { + if (!isCollectionEnabled()) { + return; + } + + if (currentScanResults == null || currentScanResults.size() == 0) { + return; + } + + long now = SystemClock.elapsedRealtime(); + + // If wifi scan recently received, then don't upload + if ((mLastWifiScanElapsedTime != 0) + && ((now - mLastWifiScanElapsedTime) <= MIN_TIME_BETWEEN_WIFI_REPORTS)) { + return; + } + + if (mWifiCurrentScanResults == null) { + mWifiCurrentScanResults = new ArrayList<ScanResult>(); + } else { + mWifiCurrentScanResults.clear(); + } + mWifiCurrentScanResults.addAll(currentScanResults); + + // If wifi has changed enough + boolean wifiHasChanged = false; + + if (mWifiLastScanResults == null) { + wifiHasChanged = true; + } else { + // Calculate the number of new AP points received + HashSet<String> previous = new HashSet<String>(); + HashSet<String> current = new HashSet<String>(); + for (ScanResult s : mWifiLastScanResults) { + previous.add(s.BSSID); + } + for (ScanResult s : mWifiCurrentScanResults) { + current.add(s.BSSID); + } + current.removeAll(previous); + + if (current.size() > + Math.min(MIN_CHANGED_WIFI_POINTS, ((mWifiCurrentScanResults.size()+1)/2))) { + wifiHasChanged = true; + } + } + + if (!wifiHasChanged) { + log("updateWifiScanResults(): Wifi results haven't changed much"); + return; + } + + if (mWifiLastScanResults == null) { + mWifiLastScanResults = new ArrayList<ScanResult>(); + } else { + mWifiLastScanResults.clear(); + } + mWifiLastScanResults.addAll(mWifiCurrentScanResults); + + mLastWifiScanElapsedTime = now; + mLastWifiScanRealTime = System.currentTimeMillis(); + + log("updateWifiScanResults(): Updated " + mWifiLastScanResults.size() + " APs"); + addToQueue(GDebugProfile.TRIGGER_WIFI_CHANGE); + } + + /** + * Updates the status of the network location provider. + * + * @param enabled true if user has enabled network location based on Google's database + * of wifi points and cell towers. + */ + public void updateNetworkProviderStatus(boolean enabled) { + mNetworkProviderIsEnabled = enabled; + } + + /** + * Updates the battery health. Battery level is healthy if there is greater than + * {@link #MIN_BATTERY_LEVEL} percentage left or if the device is plugged in + * + * @param scale maximum scale for battery + * @param level current level + * @param plugged true if device is plugged in + */ + public void updateBatteryState(int scale, int level, boolean plugged) { + mBatteryLevelIsHealthy = (plugged || (level >= (MIN_BATTERY_LEVEL * scale))); + mBatteryChargedAndPlugged = (plugged && (level >= (CHARGED_BATTERY_LEVEL * scale))); + } + + /** + * Anonymous data collection is only enabled when the user has enabled the network + * location provider, i.e. is making use of the service and if the device battery level + * is healthy. + * + * Additionally, data collection will *never* happen if the system + * property ro.com.google.enable_google_location_features is not set. + * + * @return true if anonymous location collection is enabled + */ + private boolean isCollectionEnabled() { + // This class provides a Google-specific location feature, so it's enabled only + // when the system property ro.com.google.enable_google_location_features is set. + if (!SystemProperties.get("ro.com.google.enable_google_location_features").equals("1")) { + return false; + } + return mBatteryLevelIsHealthy && mNetworkProviderIsEnabled; + } + + /** + * Adds to the MASF request queue + * + * @param trigger the event that triggered this collection event + */ + private synchronized void addToQueue(int trigger) { + + long now = SystemClock.elapsedRealtime(); + + // Include location if: + // It has been received in the last 12 minutes. + boolean includeLocation = false; + if (mLastKnownLocation != null && + (now - mLastKnownLocationTime <= MIN_VALID_LOCATION_TIME)) { + includeLocation = true; + } + + // Include wifi if: + // Wifi is new OR + // Wifi is old but last wifi upload was without location + boolean includeWifi = false; + if (trigger == GDebugProfile.TRIGGER_WIFI_CHANGE || (mWifiUploadedWithoutLocation && + includeLocation && (now - mLastWifiScanElapsedTime < MIN_VALID_LOCATION_TIME))) { + includeWifi = true; + mWifiUploadedWithoutLocation = !includeLocation; + } + + // Include cell if: + // Wifi or location information is already being included + // The cell hasn't been uploaded with the same location recently + boolean includeCell = false; + + if (mCellState != null && (includeWifi || includeLocation)) { + includeCell = true; + + if (!includeWifi && includeLocation) { + if (mCellUploads.contains(mCellState, mLastKnownLocation)) { + includeCell = false; + } + } + } + + if (!includeLocation && !includeWifi) { + log("NO UPLOAD: includeLocation=false, includeWifi=false"); + return; + } else if (!includeCell && trigger == GDebugProfile.TRIGGER_CELL_CHANGE) { + log("NO UPLOAD: includeCell=false"); + return; + } else { + log("UPLOAD: includeLocation=" + includeLocation + ", includeWifi=" + + includeWifi + ", includeCell=" + includeCell); + } + + if (includeLocation) { + // Update last uploaded location + if (mLastUploadedLocation == null) { + mLastUploadedLocation = new Location(mLastKnownLocation); + } else { + mLastUploadedLocation.set(mLastKnownLocation); + } + mLastUploadedLocationTime = now; + } + + // Immediately send output if finishing a burst for live traffic requirements + boolean immediate = false; + if (trigger == GDebugProfile.TRIGGER_COLLECTION_END_BURST|| + trigger == GDebugProfile.TRIGGER_COLLECTION_END_BURST_AT_SAME_LOCATION) { + immediate = true; + } + + try { + CellState cell = includeCell ? mCellState : null; + List<ScanResult> wifi = includeWifi ? mWifiLastScanResults : null; + Location loc = includeLocation ? mLastUploadedLocation : null; + + mMasfClient.queueCollectionReport( + trigger, loc, cell, wifi, mLastWifiScanRealTime, immediate); + + } catch(Exception e) { + Log.e(TAG, "addToQueue got exception:", e); + } + } + + private class CellUploads { + + private final int MIN_DISTANCE = MIN_DISTANCE_BETWEEN_REPORTS / 4; // 50 meters + private final int SIZE = 5; + private final String[] cells = new String[SIZE]; + private final boolean[] valid = new boolean[SIZE]; + private final double[] latitudes = new double[SIZE]; + private final double[] longitudes = new double[SIZE]; + private final float[] distance = new float[1]; + private int index = 0; + + private CellUploads() { + for (int i = 0; i < SIZE; i++) { + valid[i] = false; + } + } + + private boolean contains(CellState cellState, Location loc) { + String cell = + cellState.getCid() + ":" + cellState.getLac() + ":" + + cellState.getMnc() + ":" + cellState.getMcc(); + double lat = loc.getLatitude(); + double lng = loc.getLongitude(); + + for (int i = 0; i < SIZE; i++) { + if (valid[i] && cells[i].equals(cell)) { + Location.distanceBetween(latitudes[i], longitudes[i], lat, lng, distance); + if (distance[0] < MIN_DISTANCE) { + return true; + } + } + } + cells[index] = cell; + latitudes[index] = lat; + longitudes[index] = lng; + valid[index] = true; + + index++; + if (index == SIZE) { + index = 0; + } + return false; + } + } + + private void log(String string) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, string); + } + } +} diff --git a/location/java/com/android/internal/location/LocationMasfClient.java b/location/java/com/android/internal/location/LocationMasfClient.java new file mode 100644 index 0000000..ed9f3d6 --- /dev/null +++ b/location/java/com/android/internal/location/LocationMasfClient.java @@ -0,0 +1,1103 @@ +// Copyright 2008 The Android Open Source Project + +package com.android.internal.location; + +import com.google.common.Config; +import com.google.common.android.AndroidConfig; +import com.google.common.io.protocol.ProtoBuf; +import com.google.masf.MobileServiceMux; +import com.google.masf.ServiceCallback; +import com.google.masf.protocol.PlainRequest; +import com.google.masf.protocol.Request; + +import com.android.internal.location.protocol.GAddress; +import com.android.internal.location.protocol.GAddressComponent; +import com.android.internal.location.protocol.GAppProfile; +import com.android.internal.location.protocol.GCell; +import com.android.internal.location.protocol.GCellularPlatformProfile; +import com.android.internal.location.protocol.GCellularProfile; +import com.android.internal.location.protocol.GDebugProfile; +import com.android.internal.location.protocol.GDeviceLocation; +import com.android.internal.location.protocol.GFeature; +import com.android.internal.location.protocol.GGeocodeRequest; +import com.android.internal.location.protocol.GLatLng; +import com.android.internal.location.protocol.GLocReply; +import com.android.internal.location.protocol.GLocReplyElement; +import com.android.internal.location.protocol.GLocRequest; +import com.android.internal.location.protocol.GLocRequestElement; +import com.android.internal.location.protocol.GLocation; +import com.android.internal.location.protocol.GPlatformProfile; +import com.android.internal.location.protocol.GPrefetchMode; +import com.android.internal.location.protocol.GRectangle; +import com.android.internal.location.protocol.GWifiDevice; +import com.android.internal.location.protocol.GWifiProfile; +import com.android.internal.location.protocol.GcellularMessageTypes; +import com.android.internal.location.protocol.GdebugprofileMessageTypes; +import com.android.internal.location.protocol.GlatlngMessageTypes; +import com.android.internal.location.protocol.GlocationMessageTypes; +import com.android.internal.location.protocol.GrectangleMessageTypes; +import com.android.internal.location.protocol.GwifiMessageTypes; +import com.android.internal.location.protocol.LocserverMessageTypes; +import com.android.internal.location.protocol.ResponseCodes; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; + +import android.content.Context; +import android.location.Address; +import android.location.Location; +import android.location.LocationManager; +import android.net.wifi.ScanResult; +import android.os.Build; +import android.os.Bundle; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.EventLog; +import android.util.Log; + +/** + * Service to communicate to the Google Location Server (GLS) via MASF server + * + * {@hide} + */ +public class LocationMasfClient { + + static final String TAG = "LocationMasfClient"; + + // Address of the MASF server to connect to. + private static final String MASF_SERVER_ADDRESS = "http://www.google.com/loc/m/api"; + + // MobileServiceMux app/platform-specific values (application name matters!) + private static final String APPLICATION_NAME = "location"; + private static final String APPLICATION_VERSION = "1.0"; + private static final String PLATFORM_ID = "android"; + private static final String DISTRIBUTION_CHANNEL = "android"; + private static String PLATFORM_BUILD = null; + + // Methods exposed by the MASF server + private static final String REQUEST_QUERY_LOC = "g:loc/ql"; + private static final String REQUEST_UPLOAD_LOC = "g:loc/ul"; + + // Max time to wait for request to end + private static final long REQUEST_TIMEOUT = 5000; + + // Constant to divide Lat, Lng returned by server + private static final double E7 = 10000000.0; + + // Max wifi points to include + private static final int MAX_WIFI_TO_INCLUDE = 25; + + // Location of GLS cookie + private static final String PLATFORM_KEY_FILE = "gls.platform.key"; + private String mPlatformKey; + + // Cell cache + private LocationCache mLocationCache; + + // Location object that the cache manages + private Location mLocation = new Location(LocationManager.NETWORK_PROVIDER); + + // ProtoBuf objects we can reuse for subsequent requests + private final int MAX_COLLECTION_BUFFER_SIZE = 30; + private final long MIN_COLLECTION_INTERVAL = 15 * 60 * 1000; // 15 minutes + private ProtoBuf mPlatformProfile = null; + private ProtoBuf mCellularPlatformProfile = null; + private ProtoBuf mCurrentCollectionRequest = null; + private long mLastCollectionUploadTime = 0; + + // Objects for current request + private List<ScanResult> mWifiScanResults = new ArrayList<ScanResult>(); + private CellState mCellState = null; + private List<CellState> mCellHistory; + + // This tag is used for the event log. + private static final int COLLECTION_EVENT_LOG_TAG = 2740; + + // Extra values to designate whether location is from cache or network request + private static final String EXTRA_KEY_LOCATION_SOURCE = "networkLocationSource"; + private static final String EXTRA_VALUE_LOCATION_SOURCE_CACHED = "cached"; + private static final String EXTRA_VALUE_LOCATION_SOURCE_SERVER = "server"; + + /** + * Initializes the MobileServiceMux. Must be called before using any other function in the + * class. + */ + public LocationMasfClient(Context context) { + MobileServiceMux mux = MobileServiceMux.getSingleton(); + if (mux == null) { + AndroidConfig config = new AndroidConfig(context); + Config.setConfig(config); + + MobileServiceMux.initialize + (MASF_SERVER_ADDRESS, + APPLICATION_NAME, + APPLICATION_VERSION, + PLATFORM_ID, + DISTRIBUTION_CHANNEL); + } + mLocationCache = new LocationCache(); + + if (Build.FINGERPRINT != null) { + PLATFORM_BUILD = PLATFORM_ID + "/" + Build.FINGERPRINT; + } else { + PLATFORM_BUILD = PLATFORM_ID; + } + } + + /** + * Returns the location for the given cell or wifi information. + * + * @param apps list of apps requesting location + * @param trigger event that triggered this network request + * @param cellState cell tower state + * @param cellHistory history of acquired cell states + * @param scanResults list of wifi scan results + * @param scanTime time at which wireless scan was triggered + * @param callback function to call with received location + */ + public synchronized void getNetworkLocation(Collection<String> apps, int trigger, + CellState cellState, List<CellState> cellHistory, List<ScanResult> scanResults, + long scanTime, NetworkLocationProvider.Callback callback) { + + final NetworkLocationProvider.Callback finalCallback = callback; + + boolean foundInCache = + mLocationCache.lookup(cellState, cellHistory, scanResults, mLocation); + + if (foundInCache) { + + if (SystemClock.elapsedRealtime() - mLastCollectionUploadTime > MIN_COLLECTION_INTERVAL) { + uploadCollectionReport(true); + } + + Bundle extras = mLocation.getExtras() == null ? new Bundle() : mLocation.getExtras(); + extras.putString(EXTRA_KEY_LOCATION_SOURCE, EXTRA_VALUE_LOCATION_SOURCE_CACHED); + mLocation.setExtras(extras); + + Log.d(TAG, "getNetworkLocation(): Returning cache location with accuracy " + + mLocation.getAccuracy()); + finalCallback.locationReceived(mLocation, true); + return; + } + + Log.d(TAG, "getNetworkLocation(): Location not found in cache, making network request"); + + // Copy over to objects for this request + mWifiScanResults.clear(); + if (scanResults != null) { + mWifiScanResults.addAll(scanResults); + } + mCellState = cellState; + mCellHistory = cellHistory; + + // Create a RequestElement + ProtoBuf requestElement = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST_ELEMENT); + + // Debug profile + if (trigger != -1) { + ProtoBuf debugProfile = new ProtoBuf(GdebugprofileMessageTypes.GDEBUG_PROFILE); + debugProfile.setInt(GDebugProfile.TRIGGER, trigger); + requestElement.setProtoBuf(GLocRequestElement.DEBUG_PROFILE, debugProfile); + } + + // Cellular profile + if (mCellState != null && mCellState.isValid()) { + ProtoBuf cellularProfile = new ProtoBuf(GcellularMessageTypes.GCELLULAR_PROFILE); + cellularProfile.setLong(GCellularProfile.TIMESTAMP, mCellState.getTime()); + cellularProfile.setInt(GCellularProfile.PREFETCH_MODE, + GPrefetchMode.PREFETCH_MODE_MORE_NEIGHBORS); + + // Primary cell + ProtoBuf primaryCell = new ProtoBuf(GcellularMessageTypes.GCELL); + primaryCell.setInt(GCell.LAC, mCellState.getLac()); + primaryCell.setInt(GCell.CELLID, mCellState.getCid()); + + if ((mCellState.getMcc() != -1) && (mCellState.getMnc() != -1)) { + primaryCell.setInt(GCell.MCC, mCellState.getMcc()); + primaryCell.setInt(GCell.MNC, mCellState.getMnc()); + } + cellularProfile.setProtoBuf(GCellularProfile.PRIMARY_CELL, primaryCell); + + // History of cells + for (CellState c : cellHistory) { + ProtoBuf pastCell = new ProtoBuf(GcellularMessageTypes.GCELL); + pastCell.setInt(GCell.LAC, c.getLac()); + pastCell.setInt(GCell.CELLID, c.getCid()); + if ((c.getMcc() != -1) && (c.getMnc() != -1)) { + pastCell.setInt(GCell.MCC, c.getMcc()); + pastCell.setInt(GCell.MNC, c.getMnc()); + } + pastCell.setInt(GCell.AGE, (int)(mCellState.getTime() - c.getTime())); + cellularProfile.addProtoBuf(GCellularProfile.HISTORICAL_CELLS, pastCell); + } + + requestElement.setProtoBuf(GLocRequestElement.CELLULAR_PROFILE, cellularProfile); + } + + // Wifi profile + if (mWifiScanResults != null && mWifiScanResults.size() > 0) { + ProtoBuf wifiProfile = new ProtoBuf(GwifiMessageTypes.GWIFI_PROFILE); + wifiProfile.setLong(GWifiProfile.TIMESTAMP, scanTime); + wifiProfile.setInt(GWifiProfile.PREFETCH_MODE, + GPrefetchMode.PREFETCH_MODE_MORE_NEIGHBORS); + + int count = 0; + for (ScanResult s : mWifiScanResults) { + ProtoBuf wifiDevice = new ProtoBuf(GwifiMessageTypes.GWIFI_DEVICE); + wifiDevice.setString(GWifiDevice.MAC, s.BSSID); + wifiProfile.addProtoBuf(GWifiProfile.WIFI_DEVICES, wifiDevice); + count++; + if (count >= MAX_WIFI_TO_INCLUDE) { + break; + } + } + + requestElement.setProtoBuf(GLocRequestElement.WIFI_PROFILE, wifiProfile); + } + + // Request to send over wire + ProtoBuf request = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST); + request.addProtoBuf(GLocRequest.REQUEST_ELEMENTS, requestElement); + + // Create a Platform Profile + ProtoBuf platformProfile = createPlatformProfile(); + if (mCellState != null && mCellState.isValid()) { + // Include cellular platform Profile + ProtoBuf cellularPlatform = createCellularPlatformProfile(mCellState); + platformProfile.setProtoBuf(GPlatformProfile.CELLULAR_PLATFORM_PROFILE, + cellularPlatform); + } + request.setProtoBuf(GLocRequest.PLATFORM_PROFILE, platformProfile); + + // Include App Profiles + if (apps != null) { + for (String app : apps) { + ProtoBuf appProfile = new ProtoBuf(GlocationMessageTypes.GAPP_PROFILE); + appProfile.setString(GAppProfile.APP_NAME, app); + request.addProtoBuf(GLocRequest.APP_PROFILES, appProfile); + } + } + + // Queue any waiting collection events as well + uploadCollectionReport(false); + + ByteArrayOutputStream payload = new ByteArrayOutputStream(); + try { + request.outputTo(payload); + } catch (IOException e) { + Log.e(TAG, "getNetworkLocation(): unable to write request to payload", e); + return; + } + + // Creates request and a listener with a call back function + ProtoBuf reply = new ProtoBuf(LocserverMessageTypes.GLOC_REPLY); + Request plainRequest = + new PlainRequest(REQUEST_QUERY_LOC, (short)0, payload.toByteArray()); + + ProtoRequestListener listener = new ProtoRequestListener(reply, new ServiceCallback() { + public void onRequestComplete(Object result) { + ProtoBuf response = (ProtoBuf) result; + boolean successful = parseNetworkLocationReply(response); + finalCallback.locationReceived(mLocation, successful); + + } + }); + plainRequest.setListener(listener); + + // Send request + MobileServiceMux serviceMux = MobileServiceMux.getSingleton(); + serviceMux.submitRequest(plainRequest, true); + } + + private synchronized boolean parseNetworkLocationReply(ProtoBuf response) { + if (response == null) { + Log.e(TAG, "getNetworkLocation(): response is null"); + return false; + } + + int status1 = response.getInt(GLocReply.STATUS); + if (status1 != ResponseCodes.STATUS_STATUS_SUCCESS) { + Log.e(TAG, "getNetworkLocation(): RPC failed with status " + status1); + return false; + } + + if (response.has(GLocReply.PLATFORM_KEY)) { + String platformKey = response.getString(GLocReply.PLATFORM_KEY); + if (!TextUtils.isEmpty(platformKey)) { + setPlatformKey(platformKey); + } + } + + if (!response.has(GLocReply.REPLY_ELEMENTS)) { + Log.e(TAG, "getNetworkLocation(): no ReplyElement"); + return false; + } + ProtoBuf replyElement = response.getProtoBuf(GLocReply.REPLY_ELEMENTS); + + int status2 = replyElement.getInt(GLocReplyElement.STATUS); + if (status2 != ResponseCodes.STATUS_STATUS_SUCCESS && + status2 != ResponseCodes.STATUS_STATUS_FAILED) { + Log.e(TAG, "getNetworkLocation(): GLS failed with status " + status2); + return false; + } + + // Get prefetched data to add to the device cache + Log.d(TAG, "getNetworkLocation(): Number of prefetched entries " + + replyElement.getCount(GLocReplyElement.DEVICE_LOCATION)); + long now = System.currentTimeMillis(); + for (int i = 0; i < replyElement.getCount(GLocReplyElement.DEVICE_LOCATION); i++ ) { + ProtoBuf device = replyElement.getProtoBuf(GLocReplyElement.DEVICE_LOCATION, i); + double lat = 0; + double lng = 0; + int accuracy = -1; + int confidence = -1; + int locType = -1; + if (device.has(GDeviceLocation.LOCATION)) { + ProtoBuf deviceLocation = device.getProtoBuf(GDeviceLocation.LOCATION); + if (deviceLocation.has(GLocation.ACCURACY) && + deviceLocation.has(GLocation.LAT_LNG) + && deviceLocation.has(GLocation.CONFIDENCE)) { + lat = deviceLocation.getProtoBuf(GLocation.LAT_LNG). + getInt(GLatLng.LAT_E7) / E7; + lng = deviceLocation.getProtoBuf(GLocation.LAT_LNG). + getInt(GLatLng.LNG_E7) / E7; + accuracy = deviceLocation.getInt(GLocation.ACCURACY); + confidence = deviceLocation.getInt(GLocation.CONFIDENCE); + } + if (deviceLocation.has(GLocation.LOC_TYPE)) { + locType = deviceLocation.getInt(GLocation.LOC_TYPE); + } + } + + // Get cell key + if (device.has(GDeviceLocation.CELL) && locType != GLocation.LOCTYPE_TOWER_LOCATION) { + ProtoBuf deviceCell = device.getProtoBuf(GDeviceLocation.CELL); + int cid = deviceCell.getInt(GCell.CELLID); + int lac = deviceCell.getInt(GCell.LAC); + int mcc = -1; + int mnc = -1; + if (deviceCell.has(GCell.MNC) && deviceCell.has(GCell.MCC)) { + mcc = deviceCell.getInt(GCell.MCC); + mnc = deviceCell.getInt(GCell.MNC); + } + mLocationCache. + insert(mcc, mnc, lac, cid, lat, lng, accuracy, confidence, now); + } + + // Get wifi key + if (device.has(GDeviceLocation.WIFI_DEVICE)) { + ProtoBuf deviceWifi = device.getProtoBuf(GDeviceLocation.WIFI_DEVICE); + String bssid = deviceWifi.getString(GWifiDevice.MAC); + mLocationCache.insert(bssid, lat, lng, accuracy, confidence, now); + } + } + + mLocationCache.save(); + + // For consistent results for user, always return cache computed location + boolean foundInCache = + mLocationCache.lookup(mCellState, mCellHistory, mWifiScanResults, mLocation); + + if (foundInCache) { + + Bundle extras = mLocation.getExtras() == null ? new Bundle() : mLocation.getExtras(); + extras.putString(EXTRA_KEY_LOCATION_SOURCE, EXTRA_VALUE_LOCATION_SOURCE_SERVER); + mLocation.setExtras(extras); + + Log.d(TAG, "getNetworkLocation(): Returning network location with accuracy " + + mLocation.getAccuracy()); + return true; + } + + if (status2 == ResponseCodes.STATUS_STATUS_FAILED) { + Log.e(TAG, "getNetworkLocation(): GLS does not have location"); + // We return true here since even though there is no location, there is no need to retry + // since server doesn't have location + return true; + } + + // Get server computed location to return for now + if (!replyElement.has(GLocReplyElement.LOCATION)) { + Log.e(TAG, "getNetworkLocation(): no location in ReplyElement"); + return false; + } + ProtoBuf location = replyElement.getProtoBuf(GLocReplyElement.LOCATION); + + if (!location.has(GLocation.LAT_LNG)) { + Log.e(TAG, "getNetworkLocation(): no Lat,Lng in location"); + return false; + } + + ProtoBuf point = location.getProtoBuf(GLocation.LAT_LNG); + double lat = point.getInt(GLatLng.LAT_E7) / E7; + double lng = point.getInt(GLatLng.LNG_E7) / E7; + + int accuracy = 0; + if (location.has(GLocation.ACCURACY)) { + accuracy = location.getInt(GLocation.ACCURACY); + } + + mLocation.setLatitude(lat); + mLocation.setLongitude(lng); + mLocation.setTime(System.currentTimeMillis()); + mLocation.setAccuracy(accuracy); + + Bundle extras = mLocation.getExtras() == null ? new Bundle() : mLocation.getExtras(); + extras.putString(EXTRA_KEY_LOCATION_SOURCE, EXTRA_VALUE_LOCATION_SOURCE_SERVER); + mLocation.setExtras(extras); + + Log.e(TAG, "getNetworkLocation(): Returning *server* computed location with accuracy " + + accuracy); + + return true; + } + + /** + * Gets a reverse geocoded location from the given lat,lng point. Also attaches the name + * of the requesting application with the request + * + * @param locale locale for geocoded location + * @param appPackageName name of the package, may be null + * @param lat latitude + * @param lng longitude + * @param maxResults maximum number of addresses to return + * @param addrs the list of addresses to fill up + * @throws IOException if network is unavailable or some other issue + */ + public void reverseGeocode(Locale locale, String appPackageName, + double lat, double lng, int maxResults, List<Address> addrs) throws IOException { + + // Reverse geocoding request element + ProtoBuf requestElement = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST_ELEMENT); + + ProtoBuf latlngElement = new ProtoBuf(GlatlngMessageTypes.GLAT_LNG); + latlngElement.setInt(GLatLng.LAT_E7, (int)(lat * E7)); + latlngElement.setInt(GLatLng.LNG_E7, (int)(lng * E7)); + + ProtoBuf locationElement = new ProtoBuf(GlocationMessageTypes.GLOCATION); + locationElement.setProtoBuf(GLocation.LAT_LNG, latlngElement); + locationElement.setLong(GLocation.TIMESTAMP, System.currentTimeMillis()); + requestElement.setProtoBuf(GLocRequestElement.LOCATION, locationElement); + + ProtoBuf geocodeElement = + new ProtoBuf(LocserverMessageTypes.GGEOCODE_REQUEST); + geocodeElement.setInt(GGeocodeRequest.NUM_FEATURE_LIMIT, maxResults); + requestElement.setProtoBuf(GLocRequestElement.GEOCODE, geocodeElement); + + // Request to send over wire + ProtoBuf request = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST); + request.addProtoBuf(GLocRequest.REQUEST_ELEMENTS, requestElement); + + // Create platform profile + ProtoBuf platformProfile = createPlatformProfile(locale); + request.setProtoBuf(GLocRequest.PLATFORM_PROFILE, platformProfile); + + // Include app name + if (appPackageName != null) { + ProtoBuf appProfile = new ProtoBuf(GlocationMessageTypes.GAPP_PROFILE); + appProfile.setString(GAppProfile.APP_NAME, appPackageName); + request.setProtoBuf(GLocRequest.APP_PROFILES, appProfile); + } + + // Queue any waiting collection events as well + uploadCollectionReport(false); + + ByteArrayOutputStream payload = new ByteArrayOutputStream(); + try { + request.outputTo(payload); + } catch (IOException e) { + Log.e(TAG, "reverseGeocode(): unable to write request to payload"); + throw e; + } + + // Creates request and a listener with no callback function + ProtoBuf reply = new ProtoBuf(LocserverMessageTypes.GLOC_REPLY); + Request plainRequest = + new PlainRequest(REQUEST_QUERY_LOC, (short)0, payload.toByteArray()); + ProtoRequestListener listener = new ProtoRequestListener(reply, null); + plainRequest.setListener(listener); + + // Immediately send request and block for response until REQUEST_TIMEOUT + MobileServiceMux serviceMux = MobileServiceMux.getSingleton(); + serviceMux.submitRequest(plainRequest, true); + ProtoBuf response; + try { + response = (ProtoBuf)listener.getAsyncResult().get(REQUEST_TIMEOUT); + } catch (InterruptedException e) { + Log.e(TAG, "reverseGeocode(): response timeout"); + throw new IOException("response time-out"); + } + + if (response == null) { + throw new IOException("Unable to parse response from server"); + } + + // Parse the response + int status1 = response.getInt(GLocReply.STATUS); + if (status1 != ResponseCodes.STATUS_STATUS_SUCCESS) { + Log.e(TAG, "reverseGeocode(): RPC failed with status " + status1); + throw new IOException("RPC failed with status " + status1); + } + + if (response.has(GLocReply.PLATFORM_KEY)) { + String platformKey = response.getString(GLocReply.PLATFORM_KEY); + if (!TextUtils.isEmpty(platformKey)) { + setPlatformKey(platformKey); + } + } + + if (!response.has(GLocReply.REPLY_ELEMENTS)) { + Log.e(TAG, "reverseGeocode(): no ReplyElement"); + return; + } + ProtoBuf replyElement = response.getProtoBuf(GLocReply.REPLY_ELEMENTS); + + int status2 = replyElement.getInt(GLocReplyElement.STATUS); + if (status2 != ResponseCodes.STATUS_STATUS_SUCCESS) { + Log.e(TAG, "reverseGeocode(): GLS failed with status " + status2); + return; + } + + if (!replyElement.has(GLocReplyElement.LOCATION)) { + Log.e(TAG, "reverseGeocode(): no location in ReplyElement"); + return; + } + + ProtoBuf location = replyElement.getProtoBuf(GLocReplyElement.LOCATION); + if (!location.has(GLocation.FEATURE)) { + Log.e(TAG, "reverseGeocode(): no feature in GLocation"); + return; + } + + getAddressFromProtoBuf(location, locale, addrs); + } + + /** + * Gets a forward geocoded location from the given location string. Also attaches the name + * of the requesting application with the request + * + * Optionally, can specify the bounding box that the search results should be restricted to + * + * @param locale locale for geocoded location + * @param appPackageName name of the package, may be null + * @param locationString string to forward geocode + * @param lowerLeftLatitude latitude of lower left point of bounding box + * @param lowerLeftLongitude longitude of lower left point of bounding box + * @param upperRightLatitude latitude of upper right point of bounding box + * @param upperRightLongitude longitude of upper right point of bounding box + * @param maxResults maximum number of results to return + * @param addrs the list of addresses to fill up + * @throws IOException if network is unavailable or some other issue + */ + public void forwardGeocode(Locale locale, String appPackageName, String locationString, + double lowerLeftLatitude, double lowerLeftLongitude, + double upperRightLatitude, double upperRightLongitude, int maxResults, List<Address> addrs) + throws IOException { + + // Forward geocoding request element + ProtoBuf requestElement = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST_ELEMENT); + + ProtoBuf locationElement = new ProtoBuf(GlocationMessageTypes.GLOCATION); + locationElement.setLong(GLocation.TIMESTAMP, System.currentTimeMillis()); + locationElement.setString(GLocation.LOCATION_STRING, locationString); + requestElement.setProtoBuf(GLocRequestElement.LOCATION, locationElement); + + ProtoBuf geocodeElement = + new ProtoBuf(LocserverMessageTypes.GGEOCODE_REQUEST); + geocodeElement.setInt(GGeocodeRequest.NUM_FEATURE_LIMIT, maxResults); + + if (lowerLeftLatitude != 0 && lowerLeftLongitude !=0 && + upperRightLatitude !=0 && upperRightLongitude !=0) { + ProtoBuf lowerLeft = new ProtoBuf(GlatlngMessageTypes.GLAT_LNG); + lowerLeft.setInt(GLatLng.LAT_E7, (int)(lowerLeftLatitude * E7)); + lowerLeft.setInt(GLatLng.LNG_E7, (int)(lowerLeftLongitude * E7)); + + ProtoBuf upperRight = new ProtoBuf(GlatlngMessageTypes.GLAT_LNG); + upperRight.setInt(GLatLng.LAT_E7, (int)(upperRightLatitude * E7)); + upperRight.setInt(GLatLng.LNG_E7, (int)(upperRightLongitude * E7)); + + ProtoBuf boundingBox = new ProtoBuf(GrectangleMessageTypes.GRECTANGLE); + boundingBox.setProtoBuf(GRectangle.LOWER_LEFT, lowerLeft); + boundingBox.setProtoBuf(GRectangle.UPPER_RIGHT, upperRight); + geocodeElement.setProtoBuf(GGeocodeRequest.BOUNDING_BOX, boundingBox); + } + requestElement.setProtoBuf(GLocRequestElement.GEOCODE, geocodeElement); + + // Request to send over wire + ProtoBuf request = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST); + request.addProtoBuf(GLocRequest.REQUEST_ELEMENTS, requestElement); + + // Create platform profile + ProtoBuf platformProfile = createPlatformProfile(locale); + request.setProtoBuf(GLocRequest.PLATFORM_PROFILE, platformProfile); + + // Include app name + if (appPackageName != null) { + ProtoBuf appProfile = new ProtoBuf(GlocationMessageTypes.GAPP_PROFILE); + appProfile.setString(GAppProfile.APP_NAME, appPackageName); + request.setProtoBuf(GLocRequest.APP_PROFILES, appProfile); + } + + // Queue any waiting collection events as well + uploadCollectionReport(false); + + ByteArrayOutputStream payload = new ByteArrayOutputStream(); + try { + request.outputTo(payload); + } catch (IOException e) { + Log.e(TAG, "forwardGeocode(): unable to write request to payload"); + throw e; + } + + // Creates request and a listener with no callback function + ProtoBuf reply = new ProtoBuf(LocserverMessageTypes.GLOC_REPLY); + Request plainRequest = + new PlainRequest(REQUEST_QUERY_LOC, (short)0, payload.toByteArray()); + ProtoRequestListener listener = new ProtoRequestListener(reply, null); + plainRequest.setListener(listener); + + // Immediately send request and block for response until REQUEST_TIMEOUT + MobileServiceMux serviceMux = MobileServiceMux.getSingleton(); + serviceMux.submitRequest(plainRequest, true); + ProtoBuf response; + try { + response = (ProtoBuf)listener.getAsyncResult().get(REQUEST_TIMEOUT); + } catch (InterruptedException e) { + Log.e(TAG, "forwardGeocode(): response timeout"); + throw new IOException("response time-out"); + } + + if (response == null) { + throw new IOException("Unable to parse response from server"); + } + + // Parse the response + int status1 = response.getInt(GLocReply.STATUS); + if (status1 != ResponseCodes.STATUS_STATUS_SUCCESS) { + Log.e(TAG, "forwardGeocode(): RPC failed with status " + status1); + throw new IOException("RPC failed with status " + status1); + } + + if (response.has(GLocReply.PLATFORM_KEY)) { + String platformKey = response.getString(GLocReply.PLATFORM_KEY); + if (!TextUtils.isEmpty(platformKey)) { + setPlatformKey(platformKey); + } + } + + if (!response.has(GLocReply.REPLY_ELEMENTS)) { + Log.e(TAG, "forwardGeocode(): no ReplyElement"); + return; + } + ProtoBuf replyElement = response.getProtoBuf(GLocReply.REPLY_ELEMENTS); + + int status2 = replyElement.getInt(GLocReplyElement.STATUS); + if (status2 != ResponseCodes.STATUS_STATUS_SUCCESS) { + Log.e(TAG, "forwardGeocode(): GLS failed with status " + status2); + return; + } + + if (!replyElement.has(GLocReplyElement.LOCATION)) { + Log.e(TAG, "forwardGeocode(): no location in ReplyElement"); + return; + } + + ProtoBuf location = replyElement.getProtoBuf(GLocReplyElement.LOCATION); + if (!location.has(GLocation.FEATURE)) { + Log.e(TAG, "forwardGeocode(): no feature in GLocation"); + return; + } + + getAddressFromProtoBuf(location, locale, addrs); + } + + /** + * Queues a location collection request to be sent to the server + * + * @param trigger what triggered this collection event + * @param location last known location + * @param cellState cell tower state + * @param scanResults list of wifi points + * @param scanTime real time at which wifi scan happened + * @param immediate true if request should be sent immediately instead of being queued + */ + public synchronized void queueCollectionReport(int trigger, Location location, + CellState cellState, List<ScanResult> scanResults, long scanTime, boolean immediate) { + + // Create a RequestElement + ProtoBuf requestElement = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST_ELEMENT); + + // Include debug profile + if (trigger != -1) { + ProtoBuf debugProfile = new ProtoBuf(GdebugprofileMessageTypes.GDEBUG_PROFILE); + debugProfile.setInt(GDebugProfile.TRIGGER, trigger); + requestElement.setProtoBuf(GLocRequestElement.DEBUG_PROFILE, debugProfile); + + EventLog.writeEvent(COLLECTION_EVENT_LOG_TAG, trigger); + } + + // Include cell profile + if (cellState != null && cellState.isValid()) { + ProtoBuf cellularProfile = new ProtoBuf(GcellularMessageTypes.GCELLULAR_PROFILE); + cellularProfile.setLong(GCellularProfile.TIMESTAMP, cellState.getTime()); + + // Primary cell + ProtoBuf primaryCell = new ProtoBuf(GcellularMessageTypes.GCELL); + primaryCell.setInt(GCell.LAC, cellState.getLac()); + primaryCell.setInt(GCell.CELLID, cellState.getCid()); + if ((cellState.getMcc() != -1) && (cellState.getMnc() != -1)) { + primaryCell.setInt(GCell.MCC, cellState.getMcc()); + primaryCell.setInt(GCell.MNC, cellState.getMnc()); + } + + cellularProfile.setProtoBuf(GCellularProfile.PRIMARY_CELL, primaryCell); + requestElement.setProtoBuf(GLocRequestElement.CELLULAR_PROFILE, cellularProfile); + } + + // Include Wifi profile + if (scanResults != null && scanResults.size() > 0) { + ProtoBuf wifiProfile = new ProtoBuf(GwifiMessageTypes.GWIFI_PROFILE); + wifiProfile.setLong(GWifiProfile.TIMESTAMP, scanTime); + + int count = 0; + for (ScanResult s : scanResults) { + ProtoBuf wifiDevice = new ProtoBuf(GwifiMessageTypes.GWIFI_DEVICE); + wifiDevice.setString(GWifiDevice.MAC, s.BSSID); + wifiDevice.setString(GWifiDevice.SSID, s.SSID); + wifiDevice.setInt(GWifiDevice.RSSI, s.level); + wifiProfile.addProtoBuf(GWifiProfile.WIFI_DEVICES, wifiDevice); + count++; + if (count >= MAX_WIFI_TO_INCLUDE) { + break; + } + } + + requestElement.setProtoBuf(GLocRequestElement.WIFI_PROFILE, wifiProfile); + } + + // Location information + if (location != null) { + ProtoBuf latlngElement = new ProtoBuf(GlatlngMessageTypes.GLAT_LNG); + latlngElement.setInt(GLatLng.LAT_E7, (int)(location.getLatitude() * E7)); + latlngElement.setInt(GLatLng.LNG_E7, (int)(location.getLongitude() * E7)); + + ProtoBuf locationElement = new ProtoBuf(GlocationMessageTypes.GLOCATION); + locationElement.setProtoBuf(GLocation.LAT_LNG, latlngElement); + locationElement.setInt(GLocation.LOC_TYPE, GLocation.LOCTYPE_GPS); + locationElement.setLong(GLocation.TIMESTAMP, location.getTime()); + if (location.hasAccuracy()) { + locationElement.setInt(GLocation.ACCURACY, (int)location.getAccuracy()); + } + if (location.hasSpeed()) { + locationElement.setInt(GLocation.VELOCITY, (int)location.getSpeed()); + } + if (location.hasBearing()) { + locationElement.setInt(GLocation.HEADING, (int)location.getBearing()); + } + + requestElement.setProtoBuf(GLocRequestElement.LOCATION, locationElement); + } + + if (mCurrentCollectionRequest == null) { + mCurrentCollectionRequest = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST); + + // Create a Platform Profile + ProtoBuf platformProfile = createPlatformProfile(); + if (cellState != null && cellState.isValid()) { + ProtoBuf cellularPlatform = createCellularPlatformProfile(cellState); + platformProfile.setProtoBuf(GPlatformProfile.CELLULAR_PLATFORM_PROFILE, + cellularPlatform); + } + mCurrentCollectionRequest.setProtoBuf(GLocRequest.PLATFORM_PROFILE, platformProfile); + } + mCurrentCollectionRequest.addProtoBuf(GLocRequest.REQUEST_ELEMENTS, requestElement); + + // Immediately upload collection events if buffer exceeds certain size + if (mCurrentCollectionRequest.getCount(GLocRequest.REQUEST_ELEMENTS) + >= MAX_COLLECTION_BUFFER_SIZE) { + immediate = true; + } + + if (immediate) { + // Request to send over wire + uploadCollectionReport(immediate); + } + } + + /** + * Uploads the collection report either immediately or based on MASF's queueing logic. + * Does not need a reply back + * + * @param immediate true if request should be sent immediately instead of being queued + */ + private synchronized void uploadCollectionReport(boolean immediate) { + // There may be nothing to upload + if (mCurrentCollectionRequest == null || + mCurrentCollectionRequest.getCount(GLocRequest.REQUEST_ELEMENTS) == 0) { + return; + } + + ByteArrayOutputStream payload = new ByteArrayOutputStream(); + try { + mCurrentCollectionRequest.outputTo(payload); + } catch (IOException e) { + Log.e(TAG, "uploadCollectionReport(): unable to write request to payload"); + return; + } + + mLastCollectionUploadTime = SystemClock.elapsedRealtime(); + + // Since this has already been written to the wire, we can clear this request + int count = mCurrentCollectionRequest.getCount(GLocRequest.REQUEST_ELEMENTS); + while (count > 0) { + mCurrentCollectionRequest.remove(GLocRequest.REQUEST_ELEMENTS, count - 1); + count--; + } + + // Creates request and a listener with a call back function + ProtoBuf reply = new ProtoBuf(LocserverMessageTypes.GLOC_REPLY); + Request plainRequest = + new PlainRequest(REQUEST_UPLOAD_LOC, (short)0, payload.toByteArray()); + + ProtoRequestListener listener = new ProtoRequestListener(reply, new ServiceCallback() { + public void onRequestComplete(Object result) { + ProtoBuf response = (ProtoBuf) result; + + if (response == null) { + Log.e(TAG, "uploadCollectionReport(): response is null"); + return; + } + + int status1 = response.getInt(GLocReply.STATUS); + if (status1 != ResponseCodes.STATUS_STATUS_SUCCESS) { + Log.w(TAG, "uploadCollectionReport(): RPC failed with status " + status1); + return; + } + + if (response.has(GLocReply.PLATFORM_KEY)) { + String platformKey = response.getString(GLocReply.PLATFORM_KEY); + if (!TextUtils.isEmpty(platformKey)) { + setPlatformKey(platformKey); + } + } + + if (!response.has(GLocReply.REPLY_ELEMENTS)) { + Log.w(TAG, "uploadCollectionReport(): no ReplyElement"); + return; + } + + int count = response.getCount(GLocReply.REPLY_ELEMENTS); + for (int i = 0; i < count; i++) { + ProtoBuf replyElement = response.getProtoBuf(GLocReply.REPLY_ELEMENTS, i); + int status2 = replyElement.getInt(GLocReplyElement.STATUS); + if (status2 != ResponseCodes.STATUS_STATUS_SUCCESS) { + Log.w(TAG, "uploadCollectionReport(): GLS failed with " + status2); + } + } + + } + }); + plainRequest.setListener(listener); + + // Send request + MobileServiceMux serviceMux = MobileServiceMux.getSingleton(); + serviceMux.submitRequest(plainRequest, immediate); + + } + + private String getPlatformKey() { + if (mPlatformKey != null) { + return mPlatformKey; + } + + try { + File file = new File(LocationManager.SYSTEM_DIR, PLATFORM_KEY_FILE); + FileInputStream istream = new FileInputStream(file); + DataInputStream dataInput = new DataInputStream(istream); + String platformKey = dataInput.readUTF(); + dataInput.close(); + mPlatformKey = platformKey; + return mPlatformKey; + } catch(FileNotFoundException e) { + // No file, just ignore + return null; + } catch(IOException e) { + // Unable to read from file, just ignore + return null; + } + } + + private void setPlatformKey(String platformKey) { + File systemDir = new File(LocationManager.SYSTEM_DIR); + if (!systemDir.exists()) { + if (!systemDir.mkdirs()) { + Log.w(TAG, "setPlatformKey(): couldn't create directory"); + return; + } + } + + try { + File file = new File(LocationManager.SYSTEM_DIR, PLATFORM_KEY_FILE); + FileOutputStream ostream = new FileOutputStream(file); + DataOutputStream dataOut = new DataOutputStream(ostream); + dataOut.writeUTF(platformKey); + dataOut.close(); + mPlatformKey = platformKey; + } catch (FileNotFoundException e) { + Log.w(TAG, "setPlatformKey(): unable to create platform key file"); + } catch (IOException e) { + Log.w(TAG, "setPlatformKey(): unable to write to platform key"); + } + } + + private ProtoBuf createPlatformProfile() { + Locale locale = Locale.getDefault(); + return createPlatformProfile(locale); + } + + private ProtoBuf createPlatformProfile(Locale locale) { + if (mPlatformProfile == null) { + mPlatformProfile = new ProtoBuf(GlocationMessageTypes.GPLATFORM_PROFILE); + mPlatformProfile.setString(GPlatformProfile.VERSION, APPLICATION_VERSION); + mPlatformProfile.setString(GPlatformProfile.PLATFORM, PLATFORM_BUILD); + } + + // Add Locale + if ((locale != null) && (locale.toString() != null)) { + mPlatformProfile.setString(GPlatformProfile.LOCALE, locale.toString()); + } + + // Add Platform Key + String platformKey = getPlatformKey(); + if (!TextUtils.isEmpty(platformKey)) { + mPlatformProfile.setString(GPlatformProfile.PLATFORM_KEY, platformKey); + } + + // Clear out cellular platform profile + mPlatformProfile.setProtoBuf(GPlatformProfile.CELLULAR_PLATFORM_PROFILE, null); + + return mPlatformProfile; + } + + private ProtoBuf createCellularPlatformProfile(CellState cellState) { + if (mCellularPlatformProfile == null) { + // Radio type + int radioType = -1; + if (cellState.getRadioType() == CellState.RADIO_TYPE_GPRS) { + radioType = GCellularPlatformProfile.RADIO_TYPE_GPRS; + } else if (cellState.getRadioType() == CellState.RADIO_TYPE_CDMA) { + radioType = GCellularPlatformProfile.RADIO_TYPE_CDMA; + } else if (cellState.getRadioType() == CellState.RADIO_TYPE_WCDMA) { + radioType = GCellularPlatformProfile.RADIO_TYPE_WCDMA; + } + + // Cellular platform profile + ProtoBuf cellularPlatform = + new ProtoBuf(GlocationMessageTypes.GCELLULAR_PLATFORM_PROFILE); + cellularPlatform.setInt(GCellularPlatformProfile.RADIO_TYPE, radioType); + if ((cellState.getHomeMcc() != -1) && (cellState.getHomeMnc() != -1)) { + cellularPlatform.setInt(GCellularPlatformProfile.HOME_MCC, cellState.getHomeMcc()); + cellularPlatform.setInt(GCellularPlatformProfile.HOME_MNC, cellState.getHomeMnc()); + } + if (cellState.getCarrier() != null) { + cellularPlatform.setString(GCellularPlatformProfile.CARRIER, + cellState.getCarrier()); + } + mCellularPlatformProfile = cellularPlatform; + } + + return mCellularPlatformProfile; + } + + private void getAddressFromProtoBuf(ProtoBuf location, Locale locale, List<Address> addrs) { + + double lat = -1; + double lng = -1; + + if (location.has(GLocation.LAT_LNG)) { + ProtoBuf latlng = location.getProtoBuf(GLocation.LAT_LNG); + lat = latlng.getInt(GLatLng.LAT_E7)/E7; + lng = latlng.getInt(GLatLng.LNG_E7)/E7; + } + + for (int a = 0; a < location.getCount(GLocation.FEATURE); a++) { + + Address output = new Address(locale); + + ProtoBuf feature = location.getProtoBuf(GLocation.FEATURE, a); + output.setFeatureName(feature.getString(GFeature.NAME)); + + if (feature.has(GFeature.CENTER)) { + ProtoBuf center = feature.getProtoBuf(GFeature.CENTER); + output.setLatitude(center.getInt(GLatLng.LAT_E7)/E7); + output.setLongitude(center.getInt(GLatLng.LNG_E7)/E7); + + } else if (location.has(GLocation.LAT_LNG)) { + output.setLatitude(lat); + output.setLongitude(lng); + } + + ProtoBuf address = feature.getProtoBuf(GFeature.ADDRESS); + + for (int i = 0; i < address.getCount(GAddress.FORMATTED_ADDRESS_LINE); i++) { + String line = address.getString(GAddress.FORMATTED_ADDRESS_LINE, i); + output.setAddressLine(i, line); + } + + for (int i = 0; i < address.getCount(GAddress.COMPONENT); i++) { + ProtoBuf component = address.getProtoBuf(GAddress.COMPONENT, i); + int type = component.getInt(GAddressComponent.FEATURE_TYPE); + String name = component.getString(GAddressComponent.NAME); + + switch(type) { + case GFeature.FEATURE_TYPE_ADMINISTRATIVE_AREA : + output.setAdminArea(name); + break; + + case GFeature.FEATURE_TYPE_SUB_ADMINISTRATIVE_AREA : + output.setSubAdminArea(name); + break; + + case GFeature.FEATURE_TYPE_LOCALITY : + output.setLocality(name); + break; + + case GFeature.FEATURE_TYPE_THOROUGHFARE : + output.setThoroughfare(name); + break; + + case GFeature.FEATURE_TYPE_POST_CODE : + output.setPostalCode(name); + break; + + case GFeature.FEATURE_TYPE_COUNTRY : + output.setCountryName(name); + break; + + case GFeature.FEATURE_TYPE_COUNTRY_CODE : + output.setCountryCode(name); + break; + + default : + if (android.util.Config.LOGD) { + Log.d(TAG, "getAddressFromProtoBuf(): Ignore feature " + type + "," + name); + } + break; + } + } + + addrs.add(output); + } + } + +} diff --git a/location/java/com/android/internal/location/NetworkLocationProvider.java b/location/java/com/android/internal/location/NetworkLocationProvider.java new file mode 100644 index 0000000..7d3fda1 --- /dev/null +++ b/location/java/com/android/internal/location/NetworkLocationProvider.java @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location; + +import com.android.internal.location.protocol.GDebugProfile; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +import android.content.Context; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationManager; +import android.location.LocationProviderImpl; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.util.Log; + +/** + * A location provider which gets approximate location from Google's + * database of cell-tower and wi-fi locations. + * + * <p> It is the responsibility of the LocationManagerService to + * notify this class of any changes in the radio information + * by calling {@link #updateCellState} and of data state + * changes by calling {@link #updateNetworkState} + * + * <p> The LocationManagerService must also notify the provider + * of Wifi updates using the {@link #updateWifiScanResults} + * and {@link #updateWifiEnabledState} + * methods. + * + * <p> The provider uses whichever radio is available - Cell + * or WiFi. If neither is available, it does NOT attempt to + * switch them on. + * + * {@hide} + */ +public class NetworkLocationProvider extends LocationProviderImpl { + private static final String TAG = "NetworkLocationProvider"; + + // Wait at least 60 seconds between network queries + private static final int MIN_NETWORK_RETRY_MILLIS = 60000; + + // Max time to wait for radio update + private static final long MAX_TIME_TO_WAIT_FOR_RADIO = 5 * 1000; // 5 seconds + + // State of entire provider + private int mStatus = AVAILABLE; + private long mStatusUpdateTime = 0; + + // Network state + private int mNetworkState = TEMPORARILY_UNAVAILABLE; + + // Cell state + private static final int MAX_CELL_HISTORY_TO_KEEP = 4; + private LinkedList<CellState> mCellHistory = new LinkedList<CellState>(); + private CellState mCellState = null; + private long mLastCellStateChangeTime = 0; + private long mLastCellLockTime = 0; + + // Wifi state + private static final long MIN_TIME_BETWEEN_WIFI_REPORTS = 45 * 1000; // 45 seconds + private List<ScanResult> mWifiLastScanResults = null; + private long mLastWifiScanTriggerTime = 0; + private long mLastWifiScanElapsedTime = 0; + private long mLastWifiScanRealTime = 0; + private long mWifiScanFrequency = MIN_TIME_BETWEEN_WIFI_REPORTS; + private boolean mWifiEnabled = false; + + // Last known location state + private Location mLocation = new Location(LocationManager.NETWORK_PROVIDER); + private long mLastNetworkQueryTime = 0; // Last network request, successful or not + private long mLastSuccessfulNetworkQueryTime = 0; // Last successful network query time + + // Is provider enabled by user -- ignored by this class + private boolean mEnabled; + + // Is provider being used by an application + private HashSet<String> mApplications = new HashSet<String>(); + private boolean mTracking = false; + + // Location masf service + private LocationMasfClient mMasfClient; + + // Context of location manager service + private Context mContext; + + public static boolean isSupported() { + // This class provides a Google-specific location feature, so it's enabled only + // when the system property ro.com.google.enable_google_location_features is set. + if (!SystemProperties.get("ro.com.google.enable_google_location_features").equals("1")) { + return false; + } + + // Otherwise, assume cell location should work if we are not running in the emulator + return !SystemProperties.get("ro.kernel.qemu").equals("1"); + } + + public NetworkLocationProvider(Context context, LocationMasfClient masfClient) { + super(LocationManager.NETWORK_PROVIDER); + mContext = context; + mMasfClient = masfClient; + } + + @Override + public void updateNetworkState(int state) { + if (state == mNetworkState) { + return; + } + log("updateNetworkState(): Updating network state to " + state); + mNetworkState = state; + + updateStatus(mNetworkState); + } + + @Override + public void updateCellState(CellState newState) { + if (newState == null) { + log("updateCellState(): Cell state is invalid"); + return; + } + + if (mCellState != null && mCellState.equals(newState)) { + log("updateCellState(): Cell state is the same"); + return; + } + + // Add previous state to history + if ((mCellState != null) && mCellState.isValid()) { + if (mCellHistory.size() >= MAX_CELL_HISTORY_TO_KEEP) { + mCellHistory.remove(0); + } + mCellHistory.add(mCellState); + } + + mCellState = newState; + log("updateCellState(): Received"); + + mLastCellLockTime = 0; + mLastCellStateChangeTime = SystemClock.elapsedRealtime(); + } + + public void updateCellLockStatus(boolean acquired) { + if (acquired) { + mLastCellLockTime = SystemClock.elapsedRealtime(); + } else { + mLastCellLockTime = 0; + } + } + + @Override + public boolean requiresNetwork() { + return true; + } + + @Override + public boolean requiresSatellite() { + return false; + } + + @Override + public boolean requiresCell() { + return true; + } + + @Override + public boolean hasMonetaryCost() { + return true; + } + + @Override + public boolean supportsAltitude() { + return false; + } + + @Override + public boolean supportsSpeed() { + return false; + } + + @Override + public boolean supportsBearing() { + return false; + } + + @Override + public int getPowerRequirement() { + return Criteria.POWER_LOW; + } + + @Override + public void enable() { + // Nothing else needs to be done + mEnabled = true; + } + + @Override + public void disable() { + // Nothing else needs to be done + mEnabled = false; + } + + @Override + public boolean isEnabled() { + return mEnabled; + } + + @Override + public int getAccuracy() { + return Criteria.ACCURACY_COARSE; + } + + @Override + public int getStatus(Bundle extras) { + return mStatus; + } + + @Override + public long getStatusUpdateTime() { + return mStatusUpdateTime; + } + + @Override + public void setMinTime(long minTime) { + if (minTime < MIN_TIME_BETWEEN_WIFI_REPORTS) { + mWifiScanFrequency = MIN_TIME_BETWEEN_WIFI_REPORTS; + } else { + mWifiScanFrequency = minTime; + } + super.setMinTime(minTime); + } + + @Override + public boolean getLocation(Location l) { + + long now = SystemClock.elapsedRealtime(); + + // Trigger a wifi scan and wait for its results if necessary + if ((mWifiEnabled) && + (mWifiLastScanResults == null || + ((now - mLastWifiScanElapsedTime) > mWifiScanFrequency))) { + + boolean fallback = false; + + // If scan has been recently triggered + if (mLastWifiScanTriggerTime != 0 && + ((now - mLastWifiScanTriggerTime) < mWifiScanFrequency)) { + if ((now - mLastWifiScanTriggerTime) > MAX_TIME_TO_WAIT_FOR_RADIO) { + // If no results from last trigger available, use cell results + // This will also trigger a new scan + log("getLocation(): falling back to cell"); + fallback = true; + } else { + // Just wait for the Wifi results to be available + return false; + } + } + + WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + log("getLocation(): triggering a wifi scan"); + mLastWifiScanTriggerTime = now; + boolean succeeded = wifiManager.startScan(); + if (!succeeded) { + log("getLocation(): wifi scan did not succeed"); + // Wifi trigger failed, use cell results + fallback = true; + } + + // Wait for scan results + if (!fallback) { + return false; + } + } + + // If waiting for cell location + if (mLastCellLockTime != 0 && ((now - mLastCellLockTime) < MAX_TIME_TO_WAIT_FOR_RADIO)) { + return false; + } + + // Update Location + // 1) If there has been a cell state change + // 2) If there was no successful reply for last network request + if (mLastCellStateChangeTime > mLastNetworkQueryTime) { + updateLocation(); + return false; + + } else if ((mLastNetworkQueryTime != 0) + && (mLastNetworkQueryTime > mLastSuccessfulNetworkQueryTime) + && ((now - mLastNetworkQueryTime) > MIN_NETWORK_RETRY_MILLIS)) { + updateLocation(); + return false; + } + + if (mLocation != null && mLocation.getAccuracy() > 0) { + + // We could have a Cell Id location which hasn't changed in a + // while because we haven't switched towers so if the last location + // time + mWifiScanFrequency is less than current time update the + // locations time. + long currentTime = System.currentTimeMillis(); + if ((mLocation.getTime() + mWifiScanFrequency) < currentTime) { + mLocation.setTime(currentTime); + } + l.set(mLocation); + return true; + } else { + return false; + } + } + + @Override + public void enableLocationTracking(boolean enable) { + if (enable == mTracking) { + return; + } + + log("enableLocationTracking(): " + enable); + mTracking = enable; + + if (!enable) { + // When disabling the location provider, be sure to clear out old location + clearLocation(); + } else { + // When enabling provider, force location + forceLocation(); + } + } + + @Override + public boolean isLocationTracking() { + return mTracking; + } + + /** + * Notifies the provider that there are scan results available. + * + * @param scanResults list of wifi scan results + */ + public void updateWifiScanResults(List<ScanResult> scanResults) { + if (!mTracking) { + return; + } + + long now = SystemClock.elapsedRealtime(); + + if (scanResults == null) { + mWifiLastScanResults = null; + mLastWifiScanElapsedTime = now; + mLastWifiScanRealTime = System.currentTimeMillis(); + + log("updateWifIScanResults(): NULL APs"); + + // Force cell location since no wifi results available + if (mWifiEnabled) { + mLastCellLockTime = 0; + mLastCellStateChangeTime = SystemClock.elapsedRealtime(); + } + + } else if ((mWifiLastScanResults == null) + || (mWifiLastScanResults.size() <= 2 && scanResults.size() > mWifiLastScanResults.size()) + || ((now - mLastWifiScanElapsedTime) > mWifiScanFrequency)) { + + if (mWifiLastScanResults == null) { + mWifiLastScanResults = new ArrayList<ScanResult>(); + } else { + mWifiLastScanResults.clear(); + } + mWifiLastScanResults.addAll(scanResults); + mLastWifiScanElapsedTime = now; + mLastWifiScanRealTime = System.currentTimeMillis(); + + log("updateWifIScanResults(): " + mWifiLastScanResults.size() + " APs"); + updateLocation(); + + } + } + + /** + * Notifies the provider if Wifi has been enabled or disabled + * by the user + * + * @param enabled true if wifi is enabled; false otherwise + */ + public void updateWifiEnabledState(boolean enabled) { + mWifiEnabled = enabled; + + log("updateWifiEnabledState(): " + enabled); + + // Force location update + forceLocation(); + } + + public void addListener(String[] applications) { + if (applications != null) { + for (String app : applications) { + String a = app.replaceAll("com.google.android.", ""); + a = a.replaceAll("com.android.", ""); + mApplications.add(a); + log("addListener(): " + a); + } + } + } + + public void removeListener(String[] applications) { + if (applications != null) { + for (String app : applications) { + String a = app.replaceAll("com.google.android.", ""); + a = a.replaceAll("com.android.", ""); + mApplications.remove(a); + log("removeListener(): " + a); + } + } + } + + private void clearLocation() { + mLocation.setAccuracy(-1); + updateStatus(TEMPORARILY_UNAVAILABLE); + } + + private void forceLocation() { + if (mWifiEnabled) { + // Force another wifi scan + mWifiLastScanResults = null; + mLastWifiScanTriggerTime = 0; + mLastWifiScanElapsedTime = 0; + mLastWifiScanRealTime = 0; + } else { + // Force another cell location request + mLastCellLockTime = 0; + mLastCellStateChangeTime = SystemClock.elapsedRealtime(); + } + } + + private void updateStatus(int status) { + if (status != mStatus) { + mStatus = status; + mStatusUpdateTime = SystemClock.elapsedRealtime(); + } + } + + /** + * Gets location from the server is applications are tracking this provider + * + */ + private void updateLocation() { + + // If not being tracked, no need to do anything. + if (!mTracking) { + return; + } + + // If network is not available, can't do anything + if (mNetworkState != AVAILABLE) { + return; + } + + final long now = SystemClock.elapsedRealtime(); + + // There is a pending network request + if ((mLastNetworkQueryTime != 0) && + (mLastNetworkQueryTime > mLastSuccessfulNetworkQueryTime) && + ((now - mLastNetworkQueryTime) <= MIN_NETWORK_RETRY_MILLIS)) { + return; + } + + // Don't include wifi points if they're too old + List<ScanResult> scanResults = null; + if (mWifiEnabled && (mWifiLastScanResults != null && + ((now - mLastWifiScanElapsedTime) < (mWifiScanFrequency + MAX_TIME_TO_WAIT_FOR_RADIO)))) { + scanResults = mWifiLastScanResults; + } + + // If no valid cell information available + boolean noCell = mCellState == null || !mCellState.isValid(); + + // If no valid wifi information available + boolean noWifi = scanResults == null || (scanResults.size() == 0); + + // If no cell-id or wi-fi update, just return invalid location + if (noCell && noWifi) { + clearLocation(); + return; + } + + // What kind of a network location request was it + int trigger; + if (!mWifiEnabled) { + if (!noCell) { + trigger = GDebugProfile.TRIGGER_CELL_AND_WIFI_CHANGE; + } else { + trigger = GDebugProfile.TRIGGER_WIFI_CHANGE; + } + } else { + trigger = GDebugProfile.TRIGGER_CELL_CHANGE; + } + + try { + mLastNetworkQueryTime = now; + mMasfClient.getNetworkLocation(mApplications, trigger, mCellState, mCellHistory, + scanResults, mLastWifiScanRealTime, new Callback() { + public void locationReceived(Location location, boolean networkSuccessful) { + // If location is valid and not the same as previously known location + if ((location != null) && (location.getAccuracy() > 0) && + (location.getTime() != mLocation.getTime())) { + mLocation.set(location); + updateStatus(AVAILABLE); + } else { + // Location is unavailable + clearLocation(); + } + + // Even if no location is available, network request could have succeeded + if (networkSuccessful) { + mLastSuccessfulNetworkQueryTime = SystemClock.elapsedRealtime(); + } + + } + }); + } catch(Exception e) { + Log.e(TAG, "updateLocation got exception:", e); + } + } + + public interface Callback { + + /** + * Callback function to notify of a received network location + * + * @param location location object that is received. may be null if not a valid location + * @param successful true if network query was successful, even if no location was found + */ + void locationReceived(Location location, boolean successful); + } + + private void log(String log) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, log); + } + } + +} diff --git a/location/java/com/android/internal/location/NmeaParser.java b/location/java/com/android/internal/location/NmeaParser.java new file mode 100644 index 0000000..43afa1d --- /dev/null +++ b/location/java/com/android/internal/location/NmeaParser.java @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location; + +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import android.location.Location; +import android.os.Bundle; +import android.util.Log; + +/** + * {@hide} + */ +public class NmeaParser { + + private static final String TAG = "NmeaParser"; + + private static final TimeZone sUtcTimeZone = TimeZone.getTimeZone("UTC"); + + private static final float KNOTS_TO_METERS_PER_SECOND = 0.51444444444f; + + private final String mName; + + private int mYear = -1; + private int mMonth; + private int mDay; + + private long mTime = -1; + private long mBaseTime; + private double mLatitude; + private double mLongitude; + + private boolean mHasAltitude; + private double mAltitude; + private boolean mHasBearing; + private float mBearing; + private boolean mHasSpeed; + private float mSpeed; + + private boolean mNewWaypoint = false; + private Location mLocation = null; + private Bundle mExtras; + + public NmeaParser(String name) { + mName = name; + } + + private boolean updateTime(String time) { + if (time.length() < 6) { + return false; + } + if (mYear == -1) { + // Since we haven't seen a day/month/year yet, + // we can't construct a meaningful time stamp. + // Clean up any old data. + mLatitude = 0.0; + mLongitude = 0.0; + mHasAltitude = false; + mHasBearing = false; + mHasSpeed = false; + mExtras = null; + return false; + } + + int hour, minute; + float second; + try { + hour = Integer.parseInt(time.substring(0, 2)); + minute = Integer.parseInt(time.substring(2, 4)); + second = Float.parseFloat(time.substring(4, time.length())); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Error parsing timestamp " + time); + return false; + } + + int isecond = (int) second; + int millis = (int) ((second - isecond) * 1000); + Calendar c = new GregorianCalendar(sUtcTimeZone); + c.set(mYear, mMonth, mDay, hour, minute, isecond); + long newTime = c.getTimeInMillis() + millis; + + if (mTime == -1) { + mTime = 0; + mBaseTime = newTime; + } + newTime -= mBaseTime; + + // If the timestamp has advanced, copy the temporary data + // into a new Location + if (newTime != mTime) { + mNewWaypoint = true; + mLocation = new Location(mName); + mLocation.setTime(mTime); + mLocation.setLatitude(mLatitude); + mLocation.setLongitude(mLongitude); + if (mHasAltitude) { + mLocation.setAltitude(mAltitude); + } + if (mHasBearing) { + mLocation.setBearing(mBearing); + } + if (mHasSpeed) { + mLocation.setSpeed(mSpeed); + } + mLocation.setExtras(mExtras); + mExtras = null; + + mTime = newTime; + mHasAltitude = false; + mHasBearing = false; + mHasSpeed = false; + } + return true; + } + + private boolean updateDate(String date) { + if (date.length() != 6) { + return false; + } + int month, day, year; + try { + day = Integer.parseInt(date.substring(0, 2)); + month = Integer.parseInt(date.substring(2, 4)); + year = 2000 + Integer.parseInt(date.substring(4, 6)); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Error parsing date " + date); + return false; + } + + mYear = year; + mMonth = month; + mDay = day; + return true; + } + + private boolean updateTime(String time, String date) { + if (!updateDate(date)) { + return false; + } + return updateTime(time); + } + + private boolean updateIntExtra(String name, String value) { + int val; + try { + val = Integer.parseInt(value); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Exception parsing int " + name + ": " + value, nfe); + return false; + } + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putInt(name, val); + return true; + } + + private boolean updateFloatExtra(String name, String value) { + float val; + try { + val = Float.parseFloat(value); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Exception parsing float " + name + ": " + value, nfe); + return false; + } + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putFloat(name, val); + return true; + } + + private boolean updateDoubleExtra(String name, String value) { + double val; + try { + val = Double.parseDouble(value); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Exception parsing double " + name + ": " + value, nfe); + return false; + } + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putDouble(name, val); + return true; + } + + private double convertFromHHMM(String coord) { + double val = Double.parseDouble(coord); + int degrees = ((int) Math.floor(val)) / 100; + double minutes = val - (degrees * 100); + double dcoord = degrees + minutes / 60.0; + return dcoord; + } + + private boolean updateLatLon(String latitude, String latitudeHemi, + String longitude, String longitudeHemi) { + if (latitude.length() == 0 || longitude.length() == 0) { + return false; + } + + // Lat/long values are expressed as {D}DDMM.MMMM + double lat, lon; + try { + lat = convertFromHHMM(latitude); + if (latitudeHemi.charAt(0) == 'S') { + lat = -lat; + } + } catch (NumberFormatException nfe1) { + Log.e(TAG, "Exception parsing lat/long: " + nfe1, nfe1); + return false; + } + + try { + lon = convertFromHHMM(longitude); + if (longitudeHemi.charAt(0) == 'W') { + lon = -lon; + } + } catch (NumberFormatException nfe2) { + Log.e(TAG, "Exception parsing lat/long: " + nfe2, nfe2); + return false; + } + + // Only update if both were parsed cleanly + mLatitude = lat; + mLongitude = lon; + return true; + } + + private boolean updateAltitude(String altitude) { + if (altitude.length() == 0) { + return false; + } + double alt; + try { + alt = Double.parseDouble(altitude); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Exception parsing altitude " + altitude + ": " + nfe, + nfe); + return false; + } + + mHasAltitude = true; + mAltitude = alt; + return true; + } + + private boolean updateBearing(String bearing) { + float brg; + try { + brg = Float.parseFloat(bearing); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Exception parsing bearing " + bearing + ": " + nfe, + nfe); + return false; + } + + mHasBearing = true; + mBearing = brg; + return true; + } + + private boolean updateSpeed(String speed) { + float spd; + try { + spd = Float.parseFloat(speed) * KNOTS_TO_METERS_PER_SECOND; + } catch (NumberFormatException nfe) { + Log.e(TAG, "Exception parsing speed " + speed + ": " + nfe, nfe); + return false; + } + + mHasSpeed = true; + mSpeed = spd; + return true; + } + + public boolean parseSentence(String s) { + int len = s.length(); + if (len < 9) { + return false; + } + if (s.charAt(len - 3) == '*') { + // String checksum = s.substring(len - 4, len); + s = s.substring(0, len - 3); + } + String[] tokens = s.split(","); + String sentenceId = tokens[0].substring(3, 6); + + int idx = 1; + try { + if (sentenceId.equals("GGA")) { + String time = tokens[idx++]; + String latitude = tokens[idx++]; + String latitudeHemi = tokens[idx++]; + String longitude = tokens[idx++]; + String longitudeHemi = tokens[idx++]; + String fixQuality = tokens[idx++]; + String numSatellites = tokens[idx++]; + String horizontalDilutionOfPrecision = tokens[idx++]; + String altitude = tokens[idx++]; + String altitudeUnits = tokens[idx++]; + String heightOfGeoid = tokens[idx++]; + String heightOfGeoidUnits = tokens[idx++]; + String timeSinceLastDgpsUpdate = tokens[idx++]; + + updateTime(time); + updateLatLon(latitude, latitudeHemi, + longitude, longitudeHemi); + updateAltitude(altitude); + // updateQuality(fixQuality); + updateIntExtra("numSatellites", numSatellites); + updateFloatExtra("hdop", horizontalDilutionOfPrecision); + + if (mNewWaypoint) { + mNewWaypoint = false; + return true; + } + } else if (sentenceId.equals("GSA")) { + // DOP and active satellites + String selectionMode = tokens[idx++]; // m=manual, a=auto 2d/3d + String mode = tokens[idx++]; // 1=no fix, 2=2d, 3=3d + for (int i = 0; i < 12; i++) { + String id = tokens[idx++]; + } + String pdop = tokens[idx++]; + String hdop = tokens[idx++]; + String vdop = tokens[idx++]; + + // TODO - publish satellite ids + updateFloatExtra("pdop", pdop); + updateFloatExtra("hdop", hdop); + updateFloatExtra("vdop", vdop); + } else if (sentenceId.equals("GSV")) { + // Satellites in view + String numMessages = tokens[idx++]; + String messageNum = tokens[idx++]; + String svsInView = tokens[idx++]; + for (int i = 0; i < 4; i++) { + if (idx + 2 < tokens.length) { + String prnNumber = tokens[idx++]; + String elevation = tokens[idx++]; + String azimuth = tokens[idx++]; + if (idx < tokens.length) { + String snr = tokens[idx++]; + } + } + } + // TODO - publish this info + } else if (sentenceId.equals("RMC")) { + // Recommended minimum navigation information + String time = tokens[idx++]; + String fixStatus = tokens[idx++]; + String latitude = tokens[idx++]; + String latitudeHemi = tokens[idx++]; + String longitude = tokens[idx++]; + String longitudeHemi = tokens[idx++]; + String speed = tokens[idx++]; + String bearing = tokens[idx++]; + String utcDate = tokens[idx++]; + String magneticVariation = tokens[idx++]; + String magneticVariationDir = tokens[idx++]; + String mode = tokens[idx++]; + + if (fixStatus.charAt(0) == 'A') { + updateTime(time, utcDate); + updateLatLon(latitude, latitudeHemi, + longitude, longitudeHemi); + updateBearing(bearing); + updateSpeed(speed); + } + + if (mNewWaypoint) { + return true; + } + } else { + Log.e(TAG, "Unknown sentence: " + s); + } + } catch (ArrayIndexOutOfBoundsException e) { + // do nothing - sentence will have no effect + Log.e(TAG, "AIOOBE", e); + + for (int i = 0; i < tokens.length; i++) { + Log.e(TAG, "Got token #" + i + " = " + tokens[i]); + } + } + + return false; + } + +// } else if (sentenceId.equals("GLL")) { +// // Geographics position lat/long +// String latitude = tokens[idx++]; +// String latitudeHemi = tokens[idx++]; +// String longitude = tokens[idx++]; +// String longitudeHemi = tokens[idx++]; +// String time = tokens[idx++]; +// String status = tokens[idx++]; +// String mode = tokens[idx++]; +// String checksum = tokens[idx++]; +// +// if (status.charAt(0) == 'A') { +// updateTime(time); +// updateLatLon(latitude, latitudeHemi, longitude, longitudeHemi); +// } +//} else if (sentenceId.equals("VTG")) { +// String trackMadeGood = tokens[idx++]; +// String t = tokens[idx++]; +// String unused1 = tokens[idx++]; +// String unused2 = tokens[idx++]; +// String groundSpeedKnots = tokens[idx++]; +// String n = tokens[idx++]; +// String groundSpeedKph = tokens[idx++]; +// String k = tokens[idx++]; +// String checksum = tokens[idx++]; +// +// updateSpeed(groundSpeedKph); + + public Location getLocation() { + return mLocation; + } +} diff --git a/location/java/com/android/internal/location/ProtoRequestListener.java b/location/java/com/android/internal/location/ProtoRequestListener.java new file mode 100644 index 0000000..d73cd05 --- /dev/null +++ b/location/java/com/android/internal/location/ProtoRequestListener.java @@ -0,0 +1,61 @@ +// Copyright 2007 The Android Open Source Project + +package com.android.internal.location; + +import com.google.common.io.GoogleHttpConnection; +import com.google.common.io.protocol.ProtoBuf; +import com.google.masf.ServiceCallback; +import com.google.masf.protocol.Request; +import com.google.masf.protocol.Response; +import com.google.masf.services.AsyncResult; + +import java.io.IOException; +import java.io.InputStream; + +import android.util.Log; + +/** + * Listener for protocol buffer requests + * + * {@hide} + */ + +public class ProtoRequestListener implements Request.Listener { + private final static String TAG = "ProtoRequestListener"; + private AsyncResult result; + private ProtoBuf protoResponse; + + /** + * @return the asynchronous result object + */ + public AsyncResult getAsyncResult() { + return result; + } + + /** + * Constructor for a ProtoRequestListener + * + * @param protoResponse ProtoBuf with correct type to fill response with + * @param callback function to call after completed request (may be null) + */ + public ProtoRequestListener(ProtoBuf protoResponse, ServiceCallback callback) { + this.result = new AsyncResult(callback); + this.protoResponse = protoResponse; + } + + public boolean requestComplete(Request request, Response response) + throws IOException { + InputStream is = response.getInputStream(); + if (response.getStatusCode() == GoogleHttpConnection.HTTP_OK) { + protoResponse.parse(is); + result.setResult(protoResponse); + } else { + result.setResult(null); + } + return true; + } + + public void requestException(Request request, Exception exception) { + Log.e(TAG, "requestException()", exception); + } +} diff --git a/location/java/com/android/internal/location/TrackProvider.java b/location/java/com/android/internal/location/TrackProvider.java new file mode 100644 index 0000000..545d7dc --- /dev/null +++ b/location/java/com/android/internal/location/TrackProvider.java @@ -0,0 +1,720 @@ +// Copyright 2007 The Android Open Source Project + +package com.android.internal.location; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import android.location.Criteria; +import android.location.Location; +import android.location.LocationProviderImpl; +import android.os.Bundle; +import android.util.Config; +import android.util.Log; + +/** + * A dummy provider that returns positions interpolated from a sequence + * of caller-supplied waypoints. The waypoints are supplied as a + * String containing one or more numeric quadruples of the form: + * <br> + * <code> + * <time in millis> <latitude> <longitude> <altitude> + * </code> + * + * <p> The waypoints must be supplied in increasing timestamp order. + * + * <p> The time at which the provider is constructed is considered to + * be time 0, and further requests for positions will return a + * position that is linearly interpolated between the waypoints whose + * timestamps are closest to the amount of wall clock time that has + * elapsed since time 0. + * + * <p> Following the time of the last waypoint, the position of that + * waypoint will continue to be returned indefinitely. + * + * {@hide} + */ +public class TrackProvider extends LocationProviderImpl { + static final String LOG_TAG = "TrackProvider"; + + private static final long INTERVAL = 1000L; + + private boolean mEnabled = true; + + private double mLatitude; + private double mLongitude; + private boolean mHasAltitude; + private boolean mHasBearing; + private boolean mHasSpeed; + private double mAltitude; + private float mBearing; + private float mSpeed; + private Bundle mExtras; + + private long mBaseTime; + private long mLastTime = -1L; + private long mTime; + + private long mMinTime; + private long mMaxTime; + + private List<Waypoint> mWaypoints = new ArrayList<Waypoint>(); + private int mWaypointIndex = 0; + + private boolean mRequiresNetwork = false; + private boolean mRequiresSatellite = false; + private boolean mRequiresCell = false; + private boolean mHasMonetaryCost = false; + private boolean mSupportsAltitude = true; + private boolean mSupportsSpeed = true; + private boolean mSupportsBearing = true; + private boolean mRepeat = false; + private int mPowerRequirement = Criteria.POWER_LOW; + private int mAccuracy = Criteria.ACCURACY_COARSE; + + private float mTrackSpeed = 100.0f; // km/hr - default for kml tracks + + private Location mInitialLocation; + + private void close(Reader rdr) { + try { + if (rdr != null) { + rdr.close(); + } + } catch (IOException e) { + Log.w(LOG_TAG, "Exception closing reader", e); + } + } + + public void readTrack(File trackFile) { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(trackFile), 8192); + String s; + + long lastTime = -Long.MAX_VALUE; + while ((s = br.readLine()) != null) { + String[] tokens = s.split("\\s+"); + if (tokens.length != 4 && tokens.length != 6) { + Log.e(LOG_TAG, "Got track \"" + s + + "\", wanted <time> <long> <lat> <alt> [<bearing> <speed>]"); + continue; + } + long time; + double longitude, latitude, altitude; + try { + time = Long.parseLong(tokens[0]); + longitude = Double.parseDouble(tokens[1]); + latitude = Double.parseDouble(tokens[2]); + altitude = Double.parseDouble(tokens[3]); + } catch (NumberFormatException e) { + Log.e(LOG_TAG, "Got track \"" + s + + "\", wanted <time> <long> <lat> <alt> " + + "[<bearing> <speed>]", e); + continue; + } + + Waypoint w = new Waypoint(getName(), time, latitude, longitude, altitude); + if (tokens.length >= 6) { + float bearing, speed; + try { + bearing = Float.parseFloat(tokens[4]); + speed = Float.parseFloat(tokens[5]); + w.setBearing(bearing); + w.setSpeed(speed); + } catch (NumberFormatException e) { + Log.e(LOG_TAG, "Ignoring bearing and speed \"" + + tokens[4] + "\", \"" + tokens[5] + "\"", e); + } + } + + if (mInitialLocation == null) { + mInitialLocation = w.getLocation(); + } + + // Ignore waypoints whose time is less than or equal to 0 or + // the time of the previous waypoint + if (time < 0) { + Log.e(LOG_TAG, "Ignoring waypoint at negative time=" + time); + continue; + } + if (time <= lastTime) { + Log.e(LOG_TAG, "Ignoring waypoint at time=" + time + + " (< " + lastTime + ")"); + continue; + } + + mWaypoints.add(w); + lastTime = time; + } + + setTimes(); + return; + } catch (IOException e) { + Log.e(LOG_TAG, "Exception reading track file", e); + mWaypoints.clear(); + } finally { + close(br); + } + } + + public void readKml(File kmlFile) { + FileReader kmlReader = null; + try { + kmlReader = new FileReader(kmlFile); + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + XmlPullParser xpp = factory.newPullParser(); + xpp.setInput(kmlReader); + + // Concatenate the text of each <coordinates> tag + boolean inCoordinates = false; + StringBuilder sb = new StringBuilder(); + int eventType = xpp.getEventType(); + do { + if (eventType == XmlPullParser.START_DOCUMENT) { + // do nothing + } else if (eventType == XmlPullParser.END_DOCUMENT) { + // do nothing + } else if (eventType == XmlPullParser.START_TAG) { + String startTagName = xpp.getName(); + if (startTagName.equals("coordinates")) { + inCoordinates = true; + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTagName = xpp.getName(); + if (endTagName.equals("coordinates")) { + inCoordinates = false; + } + } else if (eventType == XmlPullParser.TEXT) { + if (inCoordinates) { + sb.append(xpp.getText()); + sb.append(' '); + } + } + eventType = xpp.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + + String coordinates = sb.toString(); + + // Parse the "lon,lat,alt" triples and supply times + // for each waypoint based on a constant speed + Location loc = null; + double KM_PER_HOUR = mTrackSpeed; + double KM_PER_METER = 1.0 / 1000.0; + double MILLIS_PER_HOUR = 60.0 * 60.0 * 1000.0; + double MILLIS_PER_METER = + (1.0 / KM_PER_HOUR) * (KM_PER_METER) * (MILLIS_PER_HOUR); + long time = 0L; + + StringTokenizer st = new StringTokenizer(coordinates, ", "); + while (st.hasMoreTokens()) { + try { + String lon = st.nextToken(); + String lat = st.nextToken(); + String alt = st.nextToken(); + if (Config.LOGD) { + Log.d(LOG_TAG, + "lon=" + lon + ", lat=" + lat + ", alt=" + alt); + } + + double nLongitude = Double.parseDouble(lon); + double nLatitude = Double.parseDouble(lat); + double nAltitude = Double.parseDouble(alt); + + Location nLoc = new Location(getName()); + nLoc.setLatitude(nLatitude); + nLoc.setLongitude(nLongitude); + if (loc != null) { + double distance = loc.distanceTo(nLoc); + if (Config.LOGD) { + Log.d(LOG_TAG, "distance = " + distance); + } + time += (long) (distance * MILLIS_PER_METER); + } + + Waypoint w = new Waypoint(getName(), time, + nLatitude, nLongitude, nAltitude); + if (supportsSpeed()) { + w.setSpeed(mTrackSpeed); + } + if (supportsBearing()) { + w.setBearing(0.0f); + } + mWaypoints.add(w); + + if (mInitialLocation == null) { + mInitialLocation = w.getLocation(); + } + + loc = nLoc; + } catch (NumberFormatException nfe) { + Log.e(LOG_TAG, "Got NumberFormatException reading KML data: " + + nfe, nfe); + } + } + + setTimes(); + return; + } catch (IOException ioe) { + mWaypoints.clear(); + Log.e(LOG_TAG, "Exception reading KML data: " + ioe, ioe); + // fall through + } catch (XmlPullParserException xppe) { + mWaypoints.clear(); + Log.e(LOG_TAG, "Exception reading KML data: " + xppe, xppe); + // fall through + } finally { + close(kmlReader); + } + } + + public void readNmea(String name, File file) { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(file), 8192); + String s; + + String provider = getName(); + NmeaParser parser = new NmeaParser(name); + while ((s = br.readLine()) != null) { + boolean newWaypoint = parser.parseSentence(s); + if (newWaypoint) { + Location loc = parser.getLocation(); + Waypoint w = new Waypoint(loc); + mWaypoints.add(w); + // Log.i(TAG, "Got waypoint " + w); + } + } + + setTimes(); + return; + } catch (IOException ioe) { + Log.e(LOG_TAG, "Exception reading NMEA data: " + ioe); + mWaypoints.clear(); + } finally { + close(br); + } + } + + private static boolean booleanVal(String tf) { + return (tf == null) || (tf.equalsIgnoreCase("true")); + } + + private static int intVal(String val) { + try { + return (val == null) ? 0 : Integer.parseInt(val); + } catch (NumberFormatException nfe) { + return 0; + } + } + + private static float floatVal(String val) { + try { + return (val == null) ? 0 : Float.parseFloat(val); + } catch (NumberFormatException nfe) { + return 0.0f; + } + } + + public void readProperties(File propertiesFile) { + BufferedReader br = null; + if (!propertiesFile.exists()) { + return; + } + try { + if (Config.LOGD) { + Log.d(LOG_TAG, "Loading properties file " + + propertiesFile.getPath()); + } + br = new BufferedReader(new FileReader(propertiesFile), 8192); + + String s; + while ((s = br.readLine()) != null) { + StringTokenizer st = new StringTokenizer(s); + String command = null; + String value = null; + if (!st.hasMoreTokens()) { + continue; + } + command = st.nextToken(); + if (st.hasMoreTokens()) { + value = st.nextToken(); + } + + if (command.equalsIgnoreCase("requiresNetwork")) { + setRequiresNetwork(booleanVal(value)); + } else if (command.equalsIgnoreCase("requiresSatellite")) { + setRequiresSatellite(booleanVal(value)); + } else if (command.equalsIgnoreCase("requiresCell")) { + setRequiresCell(booleanVal(value)); + } else if (command.equalsIgnoreCase("hasMonetaryCost")) { + setHasMonetaryCost(booleanVal(value)); + } else if (command.equalsIgnoreCase("supportsAltitude")) { + setSupportsAltitude(booleanVal(value)); + } else if (command.equalsIgnoreCase("supportsBearing")) { + setSupportsBearing(booleanVal(value)); + } else if (command.equalsIgnoreCase("repeat")) { + setRepeat(booleanVal(value)); + } else if (command.equalsIgnoreCase("supportsSpeed")) { + setSupportsSpeed(booleanVal(value)); + } else if (command.equalsIgnoreCase("powerRequirement")) { + setPowerRequirement(intVal(value)); + } else if (command.equalsIgnoreCase("accuracy")) { + setAccuracy(intVal(value)); + } else if (command.equalsIgnoreCase("trackspeed")) { + setTrackSpeed(floatVal(value)); + } else { + Log.e(LOG_TAG, "Unknown command \"" + command + "\""); + } + } + } catch (IOException ioe) { + Log.e(LOG_TAG, "IOException reading properties file " + + propertiesFile.getPath(), ioe); + } finally { + try { + if (br != null) { + br.close(); + } + } catch (IOException e) { + Log.w(LOG_TAG, "IOException closing properties file " + + propertiesFile.getPath(), e); + } + } + } + + public TrackProvider(String name) { + super(name); + setTimes(); + } + + public TrackProvider(String name, File file) { + this(name); + + String filename = file.getName(); + if (filename.endsWith("kml")) { + readKml(file); + } else if (filename.endsWith("nmea")) { + readNmea(getName(), file); + } else if (filename.endsWith("track")) { + readTrack(file); + } else { + Log.e(LOG_TAG, "Can't initialize TrackProvider from file " + + filename + " (not *kml, *nmea, or *track)"); + } + setTimes(); + } + + private void setTimes() { + mBaseTime = System.currentTimeMillis(); + if (mWaypoints.size() >= 2) { + mMinTime = mWaypoints.get(0).getTime(); + mMaxTime = mWaypoints.get(mWaypoints.size() - 1).getTime(); + } else { + mMinTime = mMaxTime = 0; + } + } + + private double interp(double d0, double d1, float frac) { + return d0 + frac * (d1 - d0); + } + + private void update() { + // Don't update the position at all unless INTERVAL milliseconds + // have passed since the last request + long time = System.currentTimeMillis() - mBaseTime; + if (time - mLastTime < INTERVAL) { + return; + } + + List<Waypoint> waypoints = mWaypoints; + if (waypoints == null) { + return; + } + int size = waypoints.size(); + if (size < 2) { + return; + } + + long t = time; + if (t < mMinTime) { + t = mMinTime; + } + if (mRepeat) { + t -= mMinTime; + long deltaT = mMaxTime - mMinTime; + t %= 2 * deltaT; + if (t > deltaT) { + t = 2 * deltaT - t; + } + t += mMinTime; + } else if (t > mMaxTime) { + t = mMaxTime; + } + + // Locate the time interval for the current time + // We slide the window since we don't expect to move + // much between calls + + Waypoint w0 = waypoints.get(mWaypointIndex); + Waypoint w1 = waypoints.get(mWaypointIndex + 1); + + // If the right end of the current interval is too early, + // move forward to the next waypoint + while (t > w1.getTime()) { + w0 = w1; + w1 = waypoints.get(++mWaypointIndex + 1); + } + // If the left end of the current interval is too late, + // move back to the previous waypoint + while (t < w0.getTime()) { + w1 = w0; + w0 = waypoints.get(--mWaypointIndex); + } + + // Now we know that w0.mTime <= t <= w1.mTime + long w0Time = w0.getTime(); + long w1Time = w1.getTime(); + long dt = w1Time - w0Time; + + float frac = (dt == 0) ? 0 : ((float) (t - w0Time) / dt); + mLatitude = interp(w0.getLatitude(), w1.getLatitude(), frac); + mLongitude = interp(w0.getLongitude(), w1.getLongitude(), frac); + mHasAltitude = w0.hasAltitude() && w1.hasAltitude(); + if (mSupportsAltitude && mHasAltitude) { + mAltitude = interp(w0.getAltitude(), w1.getAltitude(), frac); + } + if (mSupportsBearing) { + mHasBearing = frac <= 0.5f ? w0.hasBearing() : w1.hasBearing(); + if (mHasBearing) { + mBearing = frac <= 0.5f ? w0.getBearing() : w1.getBearing(); + } + } + if (mSupportsSpeed) { + mHasSpeed = frac <= 0.5f ? w0.hasSpeed() : w1.hasSpeed(); + if (mHasSpeed) { + mSpeed = frac <= 0.5f ? w0.getSpeed() : w1.getSpeed(); + } + } + mLastTime = time; + mTime = time; + } + + public void setRequiresNetwork(boolean requiresNetwork) { + mRequiresNetwork = requiresNetwork; + } + + @Override public boolean requiresNetwork() { + return mRequiresNetwork; + } + + public void setRequiresSatellite(boolean requiresSatellite) { + mRequiresSatellite = requiresSatellite; + } + + @Override public boolean requiresSatellite() { + return mRequiresSatellite; + } + + public void setRequiresCell(boolean requiresCell) { + mRequiresCell = requiresCell; + } + + @Override public boolean requiresCell() { + return mRequiresCell; + } + + public void setHasMonetaryCost(boolean hasMonetaryCost) { + mHasMonetaryCost = hasMonetaryCost; + } + + @Override public boolean hasMonetaryCost() { + return mHasMonetaryCost; + } + + public void setSupportsAltitude(boolean supportsAltitude) { + mSupportsAltitude = supportsAltitude; + } + + @Override public boolean supportsAltitude() { + return mSupportsAltitude; + } + + public void setSupportsSpeed(boolean supportsSpeed) { + mSupportsSpeed = supportsSpeed; + } + + @Override public boolean supportsSpeed() { + return mSupportsSpeed; + } + + public void setSupportsBearing(boolean supportsBearing) { + mSupportsBearing = supportsBearing; + } + + @Override public boolean supportsBearing() { + return mSupportsBearing; + } + + public void setRepeat(boolean repeat) { + mRepeat = repeat; + } + + public void setPowerRequirement(int powerRequirement) { + if (powerRequirement < Criteria.POWER_LOW || + powerRequirement > Criteria.POWER_HIGH) { + throw new IllegalArgumentException("powerRequirement = " + + powerRequirement); + } + mPowerRequirement = powerRequirement; + } + + @Override public int getPowerRequirement() { + return mPowerRequirement; + } + + public void setAccuracy(int accuracy) { + mAccuracy = accuracy; + } + + @Override public int getAccuracy() { + return mAccuracy; + } + + public void setTrackSpeed(float trackSpeed) { + mTrackSpeed = trackSpeed; + } + + @Override public void enable() { + mEnabled = true; + } + + @Override public void disable() { + mEnabled = false; + } + + @Override public boolean isEnabled() { + return mEnabled; + } + + @Override public int getStatus(Bundle extras) { + return AVAILABLE; + } + + @Override public boolean getLocation(Location l) { + if (mEnabled) { + update(); + l.setProvider(getName()); + l.setTime(mTime + mBaseTime); + l.setLatitude(mLatitude); + l.setLongitude(mLongitude); + if (mSupportsAltitude && mHasAltitude) { + l.setAltitude(mAltitude); + } + if (mSupportsBearing && mHasBearing) { + l.setBearing(mBearing); + } + if (mSupportsSpeed && mHasSpeed) { + l.setSpeed(mSpeed); + } + if (mExtras != null) { + l.setExtras(mExtras); + } + return true; + } else { + return false; + } + } + + public Location getInitialLocation() { + return mInitialLocation; + } +} + +/** + * A simple tuple of (time stamp, latitude, longitude, altitude), plus optional + * extras. + * + * {@hide} + */ +class Waypoint { + public Location mLocation; + + public Waypoint(Location location) { + mLocation = location; + } + + public Waypoint(String providerName, long time, double latitude, double longitude, + double altitude) { + mLocation = new Location(providerName); + mLocation.setTime(time); + mLocation.setLatitude(latitude); + mLocation.setLongitude(longitude); + mLocation.setAltitude(altitude); + } + + public long getTime() { + return mLocation.getTime(); + } + + public double getLatitude() { + return mLocation.getLatitude(); + } + + public double getLongitude() { + return mLocation.getLongitude(); + } + + public boolean hasAltitude() { + return mLocation.hasAltitude(); + } + + public double getAltitude() { + return mLocation.getAltitude(); + } + + public boolean hasBearing() { + return mLocation.hasBearing(); + } + + public void setBearing(float bearing) { + mLocation.setBearing(bearing); + } + + public float getBearing() { + return mLocation.getBearing(); + } + + public boolean hasSpeed() { + return mLocation.hasSpeed(); + } + + public void setSpeed(float speed) { + mLocation.setSpeed(speed); + } + + public float getSpeed() { + return mLocation.getSpeed(); + } + + public Bundle getExtras() { + return mLocation.getExtras(); + } + + public Location getLocation() { + return new Location(mLocation); + } + + @Override public String toString() { + return "Waypoint[mLocation=" + mLocation + "]"; + } +} diff --git a/location/java/com/android/internal/location/protocol/GAddress.java b/location/java/com/android/internal/location/protocol/GAddress.java new file mode 100644 index 0000000..86a3912 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GAddress.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GAddress { + static final int FORMATTED_ADDRESS_LINE = 1; + static final int COMPONENT = 2; +} + diff --git a/location/java/com/android/internal/location/protocol/GAddressComponent.java b/location/java/com/android/internal/location/protocol/GAddressComponent.java new file mode 100644 index 0000000..a06a23d --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GAddressComponent.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GAddressComponent { + static final int NAME = 1; + static final int FEATURE_TYPE = 2; +} + diff --git a/location/java/com/android/internal/location/protocol/GAppProfile.java b/location/java/com/android/internal/location/protocol/GAppProfile.java new file mode 100644 index 0000000..e3332eb --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GAppProfile.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GAppProfile { + static final int APP_NAME = 1; + static final int APP_KEY = 2; + static final int REQUEST_TYPE = 3; + static final int SEARCH_TYPE = 4; + static final int SEARCH_TERM = 5; +} + diff --git a/location/java/com/android/internal/location/protocol/GCell.java b/location/java/com/android/internal/location/protocol/GCell.java new file mode 100644 index 0000000..21d1c48 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GCell.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GCell { + static final int LAC = 1; + static final int CELLID = 2; + static final int MNC = 3; + static final int MCC = 4; + static final int RSSI = 5; + static final int AGE = 6; + static final int TIMING_ADVANCE = 7; + static final int PRIMARY_SCRAMBLING_CODE = 8; +} + diff --git a/location/java/com/android/internal/location/protocol/GCellularPlatformProfile.java b/location/java/com/android/internal/location/protocol/GCellularPlatformProfile.java new file mode 100644 index 0000000..a17da20 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GCellularPlatformProfile.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GCellularPlatformProfile { + static final int RADIO_TYPE_GPRS = 3; + static final int RADIO_TYPE_CDMA = 4; + static final int RADIO_TYPE_WCDMA = 5; + + static final int RADIO_TYPE = 1; + static final int CARRIER = 2; + static final int IP = 3; + static final int HOME_MNC = 4; + static final int HOME_MCC = 5; +} + diff --git a/location/java/com/android/internal/location/protocol/GCellularProfile.java b/location/java/com/android/internal/location/protocol/GCellularProfile.java new file mode 100644 index 0000000..8c85bf7 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GCellularProfile.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GCellularProfile { + static final int PRIMARY_CELL = 1; + static final int TIMESTAMP = 2; + static final int NEIGHBORS = 3; + static final int HISTORICAL_CELLS = 4; + static final int PREFETCH_MODE = 5; +} + diff --git a/location/java/com/android/internal/location/protocol/GDebugProfile.java b/location/java/com/android/internal/location/protocol/GDebugProfile.java new file mode 100644 index 0000000..e5c9fe6 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GDebugProfile.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GDebugProfile { + static final int TRIGGER_CELL_CHANGE = 1; + static final int TRIGGER_WIFI_CHANGE = 2; + static final int TRIGGER_CELL_AND_WIFI_CHANGE = 3; + static final int TRIGGER_GPS_CHANGE = 4; + static final int TRIGGER_OTHER = 5; + static final int TRIGGER_COLLECTION_START_BURST = 6; + static final int TRIGGER_COLLECTION_RESTART_BURST = 7; + static final int TRIGGER_COLLECTION_CONTINUE_BURST = 8; + static final int TRIGGER_COLLECTION_END_BURST = 9; + static final int TRIGGER_COLLECTION_END_BURST_AT_SAME_LOCATION = 10; + static final int TRIGGER_COLLECTION_MOVED_DISTANCE = 11; + + static final int TRIGGER = 1; + static final int ACTUAL_REQUEST = 2; + static final int CACHE_LOCATION = 3; +} + diff --git a/location/java/com/android/internal/location/protocol/GDeviceLocation.java b/location/java/com/android/internal/location/protocol/GDeviceLocation.java new file mode 100644 index 0000000..462ab07 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GDeviceLocation.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GDeviceLocation { + static final int LOCATION = 1; + static final int CELL = 2; + static final int WIFI_DEVICE = 3; +} + diff --git a/location/java/com/android/internal/location/protocol/GFeature.java b/location/java/com/android/internal/location/protocol/GFeature.java new file mode 100644 index 0000000..73fc1b3 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GFeature.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GFeature { + static final int NAME = 1; + static final int FEATURE_TYPE = 2; + static final int ADDRESS = 3; + static final int BOUNDS = 4; + static final int CENTER = 5; + + static final int FEATURE_TYPE_UNKNOWN_TYPE = 0; + static final int FEATURE_TYPE_COUNTRY = 1; + static final int FEATURE_TYPE_COUNTRY_CODE = 2; + static final int FEATURE_TYPE_ADMINISTRATIVE_AREA = 3; + static final int FEATURE_TYPE_SUB_ADMINISTRATIVE_AREA = 4; + static final int FEATURE_TYPE_LOCALITY = 5; + static final int FEATURE_TYPE_SUB_LOCALITY = 6; + static final int FEATURE_TYPE_PREMISES = 7; + static final int FEATURE_TYPE_THOROUGHFARE = 8; + static final int FEATURE_TYPE_SUB_THOROUGHFARE = 9; + static final int FEATURE_TYPE_POST_CODE = 10; +} + diff --git a/location/java/com/android/internal/location/protocol/GGeocodeRequest.java b/location/java/com/android/internal/location/protocol/GGeocodeRequest.java new file mode 100644 index 0000000..4d56cc0 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GGeocodeRequest.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GGeocodeRequest { + static final int NUM_FEATURE_LIMIT = 1; + static final int INCLUDE_BOUNDING_BOXES = 2; + static final int BOUNDING_BOX = 3; +} + diff --git a/location/java/com/android/internal/location/protocol/GGpsProfile.java b/location/java/com/android/internal/location/protocol/GGpsProfile.java new file mode 100644 index 0000000..be69eb0 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GGpsProfile.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GGpsProfile { + static final int FIX_QUALITY_INVALID = 0; + static final int FIX_QUALITY_GPS_FIX = 1; + static final int FIX_QUALITY_DGPS_FIX = 2; + + static final int GPS_FIX_TYPE = 1; + static final int PDOP = 2; + static final int HDOP = 3; + static final int VDOP = 4; +} + diff --git a/location/java/com/android/internal/location/protocol/GLatLng.java b/location/java/com/android/internal/location/protocol/GLatLng.java new file mode 100644 index 0000000..90e23df --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GLatLng.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GLatLng { + static final int LAT_E7 = 1; + static final int LNG_E7 = 2; +} + diff --git a/location/java/com/android/internal/location/protocol/GLocReply.java b/location/java/com/android/internal/location/protocol/GLocReply.java new file mode 100644 index 0000000..7a0504f --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GLocReply.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GLocReply { + static final int STATUS = 1; + static final int REPLY_ELEMENTS = 2; + static final int PLATFORM_KEY = 3; +} + diff --git a/location/java/com/android/internal/location/protocol/GLocReplyElement.java b/location/java/com/android/internal/location/protocol/GLocReplyElement.java new file mode 100644 index 0000000..bc47fcf --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GLocReplyElement.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GLocReplyElement { + static final int STATUS = 1; + static final int LOCATION = 2; + static final int DEVICE_LOCATION = 3; +} + diff --git a/location/java/com/android/internal/location/protocol/GLocRequest.java b/location/java/com/android/internal/location/protocol/GLocRequest.java new file mode 100644 index 0000000..7761c11 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GLocRequest.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GLocRequest { + static final int PLATFORM_PROFILE = 1; + static final int APP_PROFILES = 2; + static final int USER_PROFILE = 3; + static final int REQUEST_ELEMENTS = 4; + static final int MASF_CLIENT_INFO = 257; +} + diff --git a/location/java/com/android/internal/location/protocol/GLocRequestElement.java b/location/java/com/android/internal/location/protocol/GLocRequestElement.java new file mode 100644 index 0000000..d758953 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GLocRequestElement.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GLocRequestElement { + static final int CELLULAR_PROFILE = 1; + static final int WIFI_PROFILE = 2; + static final int LOCATION = 3; + static final int GEOCODE = 4; + static final int DEBUG_PROFILE = 99; +} + diff --git a/location/java/com/android/internal/location/protocol/GLocation.java b/location/java/com/android/internal/location/protocol/GLocation.java new file mode 100644 index 0000000..9a1eb1f --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GLocation.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GLocation { + static final int LOCTYPE_GPS = 0; + static final int LOCTYPE_MAPCENTER = 1; + static final int LOCTYPE_CENTROID = 2; + static final int LOCTYPE_TOWER_LOCATION = 3; + + static final int LAT_LNG = 1; + static final int SOURCE = 2; + static final int ACCURACY = 3; + static final int CONFIDENCE = 4; + static final int FEATURE = 5; + static final int TIMESTAMP = 6; + static final int OBSOLETE = 7; + static final int LOC_TYPE = 8; + static final int MISC = 9; + static final int ALTITUDE = 10; + static final int VERTICAL_ACCURACY = 11; + static final int VELOCITY = 12; + static final int HEADING = 13; + static final int GPS_PROFILE = 14; + static final int LOCATION_STRING = 15; +} + diff --git a/location/java/com/android/internal/location/protocol/GPlatformProfile.java b/location/java/com/android/internal/location/protocol/GPlatformProfile.java new file mode 100644 index 0000000..32a1f8f --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GPlatformProfile.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GPlatformProfile { + static final int VERSION = 1; + static final int PLATFORM = 2; + static final int PLATFORM_KEY = 3; + static final int DISTRIBUTION_CHANNEL = 4; + static final int LOCALE = 5; + static final int CELLULAR_PLATFORM_PROFILE = 6; + static final int WIFI_PLATFORM_PROFILE = 7; +} + diff --git a/location/java/com/android/internal/location/protocol/GPrefetchMode.java b/location/java/com/android/internal/location/protocol/GPrefetchMode.java new file mode 100644 index 0000000..041b686 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GPrefetchMode.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GPrefetchMode { + static final int PREFETCH_MODE_NO_PREFETCH = 0; + static final int PREFETCH_MODE_REQUESTED_NEIGHBORS_ONLY = 1; + static final int PREFETCH_MODE_MORE_NEIGHBORS = 2; + +} + diff --git a/location/java/com/android/internal/location/protocol/GRectangle.java b/location/java/com/android/internal/location/protocol/GRectangle.java new file mode 100644 index 0000000..b8412e6 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GRectangle.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GRectangle { + static final int LOWER_LEFT = 1; + static final int UPPER_RIGHT = 2; +} + diff --git a/location/java/com/android/internal/location/protocol/GUserProfile.java b/location/java/com/android/internal/location/protocol/GUserProfile.java new file mode 100644 index 0000000..2ce962c --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GUserProfile.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GUserProfile { + static final int USER_NAME = 1; + static final int AUTH_TOKEN = 2; +} + diff --git a/location/java/com/android/internal/location/protocol/GWifiDevice.java b/location/java/com/android/internal/location/protocol/GWifiDevice.java new file mode 100644 index 0000000..62bd03a --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GWifiDevice.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GWifiDevice { + static final int MAC = 1; + static final int SSID = 2; + static final int CHANNEL = 3; + static final int RSSI = 4; + static final int NOISE = 5; +} + diff --git a/location/java/com/android/internal/location/protocol/GWifiPlatformProfile.java b/location/java/com/android/internal/location/protocol/GWifiPlatformProfile.java new file mode 100644 index 0000000..7f1efcb --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GWifiPlatformProfile.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GWifiPlatformProfile { + static final int RADIO_TYPE_WIFI802_11_A = 1; + static final int RADIO_TYPE_WIFI802_11_B = 2; + static final int RADIO_TYPE_WIFI802_11_G = 3; + static final int RADIO_TYPE_WIFI802_11_N = 4; + + static final int SCANNER_MAC = 1; + static final int SCANNER_IP = 2; + static final int RADIO_TYPE = 3; +} + diff --git a/location/java/com/android/internal/location/protocol/GWifiProfile.java b/location/java/com/android/internal/location/protocol/GWifiProfile.java new file mode 100644 index 0000000..e731027 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GWifiProfile.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface GWifiProfile { + static final int TIMESTAMP = 1; + static final int WIFI_DEVICES = 2; + static final int PREFETCH_MODE = 3; +} + diff --git a/location/java/com/android/internal/location/protocol/GaddressMessageTypes.java b/location/java/com/android/internal/location/protocol/GaddressMessageTypes.java new file mode 100644 index 0000000..7b6ffd0 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GaddressMessageTypes.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBufType; + +public class GaddressMessageTypes { + public static final ProtoBufType GADDRESS = new ProtoBufType(); + public static final ProtoBufType GADDRESS_COMPONENT = new ProtoBufType(); + + static { + GADDRESS + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_DATA, + GAddress.FORMATTED_ADDRESS_LINE, null) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GAddress.COMPONENT, GADDRESS_COMPONENT); + + GADDRESS_COMPONENT + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_DATA, + GAddressComponent.NAME, null) + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT32, + GAddressComponent.FEATURE_TYPE, null); + + } +} diff --git a/location/java/com/android/internal/location/protocol/GcellularMessageTypes.java b/location/java/com/android/internal/location/protocol/GcellularMessageTypes.java new file mode 100644 index 0000000..37a6d52 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GcellularMessageTypes.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBufType; + +public class GcellularMessageTypes { + public static final ProtoBufType GCELL = new ProtoBufType(); + public static final ProtoBufType GCELLULAR_PROFILE = new ProtoBufType(); + + static { + GCELL + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT32, + GCell.LAC, null) + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT32, + GCell.CELLID, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCell.MNC, new Long(-1)) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCell.MCC, new Long(-1)) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCell.RSSI, new Long(-9999)) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCell.AGE, new Long(0)) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCell.TIMING_ADVANCE, new Long(-1)) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCell.PRIMARY_SCRAMBLING_CODE, null); + + GCELLULAR_PROFILE + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_MESSAGE, + GCellularProfile.PRIMARY_CELL, GCELL) + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT64, + GCellularProfile.TIMESTAMP, null) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GCellularProfile.NEIGHBORS, GCELL) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GCellularProfile.HISTORICAL_CELLS, GCELL) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCellularProfile.PREFETCH_MODE, null); + + } +} diff --git a/location/java/com/android/internal/location/protocol/GdebugprofileMessageTypes.java b/location/java/com/android/internal/location/protocol/GdebugprofileMessageTypes.java new file mode 100644 index 0000000..faf5b89 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GdebugprofileMessageTypes.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBuf; +import com.google.common.io.protocol.ProtoBufType; + +public class GdebugprofileMessageTypes { + public static final ProtoBufType GDEBUG_PROFILE = new ProtoBufType(); + + static { + GDEBUG_PROFILE + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GDebugProfile.TRIGGER, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_BOOL, + GDebugProfile.ACTUAL_REQUEST, ProtoBuf.TRUE) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GDebugProfile.CACHE_LOCATION, GlocationMessageTypes.GDEVICE_LOCATION); + + } +} diff --git a/location/java/com/android/internal/location/protocol/GfeatureMessageTypes.java b/location/java/com/android/internal/location/protocol/GfeatureMessageTypes.java new file mode 100644 index 0000000..24b182a --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GfeatureMessageTypes.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBufType; + +public class GfeatureMessageTypes { + public static final ProtoBufType GFEATURE_TYPE = new ProtoBufType(); + public static final ProtoBufType GFEATURE = new ProtoBufType(); + + static { + GFEATURE + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_DATA, + GFeature.NAME, null) + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT32, + GFeature.FEATURE_TYPE, null) + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_MESSAGE, + GFeature.ADDRESS, GaddressMessageTypes.GADDRESS) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GFeature.BOUNDS, GrectangleMessageTypes.GRECTANGLE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GFeature.CENTER, GlatlngMessageTypes.GLAT_LNG); + + } +} diff --git a/location/java/com/android/internal/location/protocol/GlatlngMessageTypes.java b/location/java/com/android/internal/location/protocol/GlatlngMessageTypes.java new file mode 100644 index 0000000..b6a9086 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GlatlngMessageTypes.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBufType; + +public class GlatlngMessageTypes { + public static final ProtoBufType GLAT_LNG = new ProtoBufType(); + + static { + GLAT_LNG + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_FIXED32, + GLatLng.LAT_E7, null) + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_FIXED32, + GLatLng.LNG_E7, null); + + } +} diff --git a/location/java/com/android/internal/location/protocol/GlocationMessageTypes.java b/location/java/com/android/internal/location/protocol/GlocationMessageTypes.java new file mode 100644 index 0000000..067d47c --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GlocationMessageTypes.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBufType; + +public class GlocationMessageTypes { + public static final ProtoBufType GGPS_PROFILE = new ProtoBufType(); + public static final ProtoBufType GLOCATION = new ProtoBufType(); + public static final ProtoBufType GDEVICE_LOCATION = new ProtoBufType(); + public static final ProtoBufType GCELLULAR_PLATFORM_PROFILE = new ProtoBufType(); + public static final ProtoBufType GWIFI_PLATFORM_PROFILE = new ProtoBufType(); + public static final ProtoBufType GPREFETCH_MODE = new ProtoBufType(); + public static final ProtoBufType GPLATFORM_PROFILE = new ProtoBufType(); + public static final ProtoBufType GAPP_PROFILE = new ProtoBufType(); + public static final ProtoBufType GUSER_PROFILE = new ProtoBufType(); + + static { + GGPS_PROFILE + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GGpsProfile.GPS_FIX_TYPE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GGpsProfile.PDOP, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GGpsProfile.HDOP, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GGpsProfile.VDOP, null); + + GLOCATION + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocation.LAT_LNG, GlatlngMessageTypes.GLAT_LNG) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GLocation.SOURCE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GLocation.ACCURACY, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GLocation.CONFIDENCE, null) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GLocation.FEATURE, GfeatureMessageTypes.GFEATURE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT64, + GLocation.TIMESTAMP, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_BOOL, + GLocation.OBSOLETE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GLocation.LOC_TYPE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GLocation.MISC, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GLocation.ALTITUDE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GLocation.VERTICAL_ACCURACY, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GLocation.VELOCITY, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GLocation.HEADING, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocation.GPS_PROFILE, GGPS_PROFILE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GLocation.LOCATION_STRING, null); + + GDEVICE_LOCATION + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GDeviceLocation.LOCATION, GLOCATION) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GDeviceLocation.CELL, GcellularMessageTypes.GCELL) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GDeviceLocation.WIFI_DEVICE, GwifiMessageTypes.GWIFI_DEVICE); + + GCELLULAR_PLATFORM_PROFILE + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCellularPlatformProfile.RADIO_TYPE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GCellularPlatformProfile.CARRIER, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GCellularPlatformProfile.IP, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCellularPlatformProfile.HOME_MNC, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCellularPlatformProfile.HOME_MCC, null); + + GWIFI_PLATFORM_PROFILE + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GWifiPlatformProfile.SCANNER_MAC, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GWifiPlatformProfile.SCANNER_IP, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GWifiPlatformProfile.RADIO_TYPE, null); + + GPLATFORM_PROFILE + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_DATA, + GPlatformProfile.VERSION, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GPlatformProfile.PLATFORM, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GPlatformProfile.PLATFORM_KEY, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GPlatformProfile.DISTRIBUTION_CHANNEL, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GPlatformProfile.LOCALE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GPlatformProfile.CELLULAR_PLATFORM_PROFILE, GCELLULAR_PLATFORM_PROFILE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GPlatformProfile.WIFI_PLATFORM_PROFILE, GWIFI_PLATFORM_PROFILE); + + GAPP_PROFILE + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GAppProfile.APP_NAME, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GAppProfile.APP_KEY, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GAppProfile.REQUEST_TYPE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GAppProfile.SEARCH_TYPE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GAppProfile.SEARCH_TERM, null); + + GUSER_PROFILE + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GUserProfile.USER_NAME, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GUserProfile.AUTH_TOKEN, null); + + } +} diff --git a/location/java/com/android/internal/location/protocol/GrectangleMessageTypes.java b/location/java/com/android/internal/location/protocol/GrectangleMessageTypes.java new file mode 100644 index 0000000..aeb0047 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GrectangleMessageTypes.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBufType; + +public class GrectangleMessageTypes { + public static final ProtoBufType GRECTANGLE = new ProtoBufType(); + + static { + GRECTANGLE + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_MESSAGE, + GRectangle.LOWER_LEFT, GlatlngMessageTypes.GLAT_LNG) + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_MESSAGE, + GRectangle.UPPER_RIGHT, GlatlngMessageTypes.GLAT_LNG); + + } +} diff --git a/location/java/com/android/internal/location/protocol/GwifiMessageTypes.java b/location/java/com/android/internal/location/protocol/GwifiMessageTypes.java new file mode 100644 index 0000000..cd7119b --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GwifiMessageTypes.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBufType; + +public class GwifiMessageTypes { + public static final ProtoBufType GWIFI_DEVICE = new ProtoBufType(); + public static final ProtoBufType GWIFI_PROFILE = new ProtoBufType(); + + static { + GWIFI_DEVICE + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_DATA, + GWifiDevice.MAC, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GWifiDevice.SSID, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GWifiDevice.CHANNEL, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GWifiDevice.RSSI, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GWifiDevice.NOISE, null); + + GWIFI_PROFILE + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT64, + GWifiProfile.TIMESTAMP, null) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GWifiProfile.WIFI_DEVICES, GWIFI_DEVICE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GWifiProfile.PREFETCH_MODE, null); + + } +} diff --git a/location/java/com/android/internal/location/protocol/LocserverMessageTypes.java b/location/java/com/android/internal/location/protocol/LocserverMessageTypes.java new file mode 100644 index 0000000..8ffd004 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/LocserverMessageTypes.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBuf; +import com.google.common.io.protocol.ProtoBufType; + +public class LocserverMessageTypes { + public static final ProtoBufType RESPONSE_CODES = new ProtoBufType(); + public static final ProtoBufType GLOC_REQUEST_ELEMENT = new ProtoBufType(); + public static final ProtoBufType GLOC_REQUEST = new ProtoBufType(); + public static final ProtoBufType GGEOCODE_REQUEST = new ProtoBufType(); + public static final ProtoBufType GLOC_REPLY_ELEMENT = new ProtoBufType(); + public static final ProtoBufType GLOC_REPLY = new ProtoBufType(); + + static { + GLOC_REQUEST_ELEMENT + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocRequestElement.CELLULAR_PROFILE, GcellularMessageTypes.GCELLULAR_PROFILE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocRequestElement.WIFI_PROFILE, GwifiMessageTypes.GWIFI_PROFILE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocRequestElement.LOCATION, GlocationMessageTypes.GLOCATION) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocRequestElement.GEOCODE, GGEOCODE_REQUEST) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocRequestElement.DEBUG_PROFILE, GdebugprofileMessageTypes.GDEBUG_PROFILE); + + GLOC_REQUEST + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_MESSAGE, + GLocRequest.PLATFORM_PROFILE, GlocationMessageTypes.GPLATFORM_PROFILE) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GLocRequest.APP_PROFILES, GlocationMessageTypes.GAPP_PROFILE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocRequest.USER_PROFILE, GlocationMessageTypes.GUSER_PROFILE) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GLocRequest.REQUEST_ELEMENTS, GLOC_REQUEST_ELEMENT) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocRequest.MASF_CLIENT_INFO, null); + + GGEOCODE_REQUEST + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_FIXED32, + GGeocodeRequest.NUM_FEATURE_LIMIT, new Long(1)) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_BOOL, + GGeocodeRequest.INCLUDE_BOUNDING_BOXES, ProtoBuf.FALSE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GGeocodeRequest.BOUNDING_BOX, GrectangleMessageTypes.GRECTANGLE); + + GLOC_REPLY_ELEMENT + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT32, + GLocReplyElement.STATUS, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocReplyElement.LOCATION, GlocationMessageTypes.GLOCATION) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GLocReplyElement.DEVICE_LOCATION, GlocationMessageTypes.GDEVICE_LOCATION); + + GLOC_REPLY + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT32, + GLocReply.STATUS, null) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GLocReply.REPLY_ELEMENTS, GLOC_REPLY_ELEMENT) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GLocReply.PLATFORM_KEY, null); + + } +} diff --git a/location/java/com/android/internal/location/protocol/ResponseCodes.java b/location/java/com/android/internal/location/protocol/ResponseCodes.java new file mode 100644 index 0000000..2ea9318 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/ResponseCodes.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location.protocol; + +public interface ResponseCodes { + static final int STATUS_STATUS_SUCCESS = 0; + static final int STATUS_STATUS_FAILED = 1; + static final int STATUS_AUTHORIZATION_REJECTED = 2; + static final int STATUS_NO_SOURCE_EXISTS = 3; + static final int STATUS_SIGNAL_TOO_WEAK = 4; + static final int STATUS_INVALID_REQUEST = 5; + static final int STATUS_INVALID_NUM_REQUESTS = 6; + static final int STATUS_INVALID_USERLOCATION_FORMAT = 7; + static final int STATUS_INVALID_OPERATION_CODE = 8; + static final int STATUS_INVALID_MAC_STRING_FORMAT = 9; + static final int STATUS_INVALID_CELLID_STRING_FORMAT = 10; + static final int STATUS_NON_EXISTENT_AP = 11; + static final int STATUS_NON_EXISTENT_CELLID = 12; + static final int STATUS_STATUS_FAILED_NO_SOURCE = 13; + static final int STATUS_STATUS_FAILED_NO_SAVE = 14; + static final int STATUS_PLATFORM_KEY_EXPIRED = 15; + static final int STATUS_NO_STORE_EXISTS = 16; + static final int STATUS_NO_CELLIDDATA_FOR_UPDATE = 17; + static final int STATUS_NON_SUPPORTED_OPERATION_IN_UPDATE = 18; + static final int STATUS_NON_SUPPORTED_OPERATION = 19; + static final int STATUS_STATUS_FAILED_NO_GEOCODE = 20; + static final int STATUS_BLACKLISTED_IP_CELLID = 100; + static final int STATUS_BLACKLISTED_IP_WIFI = 101; + +} + |
