diff options
Diffstat (limited to 'services/java/com/android')
14 files changed, 2674 insertions, 49 deletions
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index d67dde0..2c388ee 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -421,7 +421,7 @@ class BackupManagerService extends IBackupManager.Stub { Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0; // If Encrypted file systems is enabled or disabled, this call will return the // correct directory. - mBaseStateDir = new File(Environment.getDataDirectory(), "backup"); + mBaseStateDir = new File(Environment.getSecureDataDirectory(), "backup"); mBaseStateDir.mkdirs(); mDataDir = Environment.getDownloadCacheDirectory(); diff --git a/services/java/com/android/server/Installer.java b/services/java/com/android/server/Installer.java index 2eaa58c..1f34eba 100644 --- a/services/java/com/android/server/Installer.java +++ b/services/java/com/android/server/Installer.java @@ -166,11 +166,17 @@ class Installer { } } - public int install(String name, int uid, int gid) { + public int install(String name, boolean useEncryptedFilesystem, int uid, int gid) { StringBuilder builder = new StringBuilder("install"); builder.append(' '); builder.append(name); builder.append(' '); + if (useEncryptedFilesystem) { + builder.append('1'); + } else { + builder.append('0'); + } + builder.append(' '); builder.append(uid); builder.append(' '); builder.append(gid); @@ -203,33 +209,57 @@ class Installer { return execute(builder.toString()); } - public int remove(String name) { + public int remove(String name, boolean useEncryptedFilesystem) { StringBuilder builder = new StringBuilder("remove"); builder.append(' '); builder.append(name); + builder.append(' '); + if (useEncryptedFilesystem) { + builder.append('1'); + } else { + builder.append('0'); + } return execute(builder.toString()); } - public int rename(String oldname, String newname) { + public int rename(String oldname, String newname, boolean useEncryptedFilesystem) { StringBuilder builder = new StringBuilder("rename"); builder.append(' '); builder.append(oldname); builder.append(' '); builder.append(newname); + builder.append(' '); + if (useEncryptedFilesystem) { + builder.append('1'); + } else { + builder.append('0'); + } return execute(builder.toString()); } - public int deleteCacheFiles(String name) { + public int deleteCacheFiles(String name, boolean useEncryptedFilesystem) { StringBuilder builder = new StringBuilder("rmcache"); builder.append(' '); builder.append(name); + builder.append(' '); + if (useEncryptedFilesystem) { + builder.append('1'); + } else { + builder.append('0'); + } return execute(builder.toString()); } - public int clearUserData(String name) { + public int clearUserData(String name, boolean useEncryptedFilesystem) { StringBuilder builder = new StringBuilder("rmuserdata"); builder.append(' '); builder.append(name); + builder.append(' '); + if (useEncryptedFilesystem) { + builder.append('1'); + } else { + builder.append('0'); + } return execute(builder.toString()); } @@ -263,7 +293,7 @@ class Installer { } public int getSizeInfo(String pkgName, String apkPath, - String fwdLockApkPath, PackageStats pStats) { + String fwdLockApkPath, PackageStats pStats, boolean useEncryptedFilesystem) { StringBuilder builder = new StringBuilder("getsize"); builder.append(' '); builder.append(pkgName); @@ -271,6 +301,13 @@ class Installer { builder.append(apkPath); builder.append(' '); builder.append(fwdLockApkPath != null ? fwdLockApkPath : "!"); + builder.append(' '); + if (useEncryptedFilesystem) { + builder.append('1'); + } else { + builder.append('0'); + } + String s = transaction(builder.toString()); String res[] = s.split(" "); diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 65f4194..33b0e81 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -16,17 +16,6 @@ package com.android.server; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Observable; -import java.util.Observer; -import java.util.Set; - import android.app.Activity; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -50,7 +39,6 @@ import android.location.INetInitiatedListener; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; -import android.location.LocationProviderInterface; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; @@ -68,12 +56,25 @@ import android.util.Log; import android.util.Slog; import android.util.PrintWriterPrinter; -import com.android.internal.location.GeocoderProxy; -import com.android.internal.location.GpsLocationProvider; import com.android.internal.location.GpsNetInitiatedHandler; -import com.android.internal.location.LocationProviderProxy; -import com.android.internal.location.MockProvider; -import com.android.internal.location.PassiveProvider; + +import com.android.server.location.GeocoderProxy; +import com.android.server.location.GpsLocationProvider; +import com.android.server.location.LocationProviderInterface; +import com.android.server.location.LocationProviderProxy; +import com.android.server.location.MockProvider; +import com.android.server.location.PassiveProvider; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; /** * The service class that manages LocationProviders and issues location diff --git a/services/java/com/android/server/MasterClearReceiver.java b/services/java/com/android/server/MasterClearReceiver.java index 27a8a74..4d04cee 100644 --- a/services/java/com/android/server/MasterClearReceiver.java +++ b/services/java/com/android/server/MasterClearReceiver.java @@ -39,7 +39,11 @@ public class MasterClearReceiver extends BroadcastReceiver { try { Slog.w(TAG, "!!! FACTORY RESET !!!"); - RecoverySystem.rebootWipeUserData(context); + if (intent.hasExtra("enableEFS")) { + RecoverySystem.rebootToggleEFS(context, intent.getBooleanExtra("enableEFS", false)); + } else { + RecoverySystem.rebootWipeUserData(context); + } Log.wtf(TAG, "Still running after master clear?!"); } catch (IOException e) { Slog.e(TAG, "Can't perform master clear/factory reset", e); diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index df93215..599023c 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -150,6 +150,8 @@ class PackageManagerService extends IPackageManager.Stub { private static final boolean GET_CERTIFICATES = true; + private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled"; + private static final int REMOVE_EVENTS = FileObserver.CLOSE_WRITE | FileObserver.DELETE | FileObserver.MOVED_FROM; private static final int ADD_EVENTS = @@ -204,6 +206,10 @@ class PackageManagerService extends IPackageManager.Stub { // This is where all application persistent data goes. final File mAppDataDir; + // If Encrypted File System feature is enabled, all application persistent data + // should go here instead. + final File mSecureAppDataDir; + // This is the object monitoring the framework dir. final FileObserver mFrameworkInstallObserver; @@ -768,6 +774,7 @@ class PackageManagerService extends IPackageManager.Stub { File dataDir = Environment.getDataDirectory(); mAppDataDir = new File(dataDir, "data"); + mSecureAppDataDir = new File(dataDir, "secure/data"); mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); if (mInstaller == null) { @@ -777,6 +784,7 @@ class PackageManagerService extends IPackageManager.Stub { File miscDir = new File(dataDir, "misc"); miscDir.mkdirs(); mAppDataDir.mkdirs(); + mSecureAppDataDir.mkdirs(); mDrmAppPrivateInstallDir.mkdirs(); } @@ -937,7 +945,9 @@ class PackageManagerService extends IPackageManager.Stub { + " no longer exists; wiping its data"; reportSettingsProblem(Log.WARN, msg); if (mInstaller != null) { - mInstaller.remove(ps.name); + // XXX how to set useEncryptedFSDir for packages that + // are not encrypted? + mInstaller.remove(ps.name, true); } } } @@ -1020,7 +1030,8 @@ class PackageManagerService extends IPackageManager.Stub { void cleanupInstallFailedPackage(PackageSetting ps) { Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name); if (mInstaller != null) { - int retCode = mInstaller.remove(ps.name); + boolean useSecureFS = useEncryptedFilesystemForPackage(ps.pkg); + int retCode = mInstaller.remove(ps.name, useSecureFS); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data directory for package: " + ps.name + ", retcode=" + retCode); @@ -1718,6 +1729,7 @@ class PackageManagerService extends IPackageManager.Stub { static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) { if (pi1.icon != pi2.icon) return false; + if (pi1.logo != pi2.logo) return false; if (pi1.protectionLevel != pi2.protectionLevel) return false; if (!compareStrings(pi1.name, pi2.name)) return false; if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false; @@ -2743,6 +2755,11 @@ class PackageManagerService extends IPackageManager.Stub { return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED; } + + private static boolean useEncryptedFilesystemForPackage(PackageParser.Package pkg) { + return Environment.isEncryptedFilesystemEnabled() && + ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_NEVER_ENCRYPT) == 0); + } private boolean verifyPackageUpdate(PackageSetting oldPkg, PackageParser.Package newPkg) { if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { @@ -2760,7 +2777,14 @@ class PackageManagerService extends IPackageManager.Stub { } private File getDataPathForPackage(PackageParser.Package pkg) { - return new File(mAppDataDir, pkg.packageName); + boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg); + File dataPath; + if (useEncryptedFSDir) { + dataPath = new File(mSecureAppDataDir, pkg.packageName); + } else { + dataPath = new File(mAppDataDir, pkg.packageName); + } + return dataPath; } private PackageParser.Package scanPackageLI(PackageParser.Package pkg, @@ -3111,6 +3135,7 @@ class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.dataDir = dataPath.getPath(); } else { // This is a normal package, need to make its data directory. + boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg); dataPath = getDataPathForPackage(pkg); boolean uidError = false; @@ -3127,7 +3152,7 @@ class PackageManagerService extends IPackageManager.Stub { // If this is a system app, we can at least delete its // current data so the application will still work. if (mInstaller != null) { - int ret = mInstaller.remove(pkgName); + int ret = mInstaller.remove(pkgName, useEncryptedFSDir); if (ret >= 0) { // Old data gone! String msg = "System package " + pkg.packageName @@ -3138,7 +3163,7 @@ class PackageManagerService extends IPackageManager.Stub { recovered = true; // And now re-install the app. - ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, + ret = mInstaller.install(pkgName, useEncryptedFSDir, pkg.applicationInfo.uid, pkg.applicationInfo.uid); if (ret == -1) { // Ack should not happen! @@ -3178,7 +3203,7 @@ class PackageManagerService extends IPackageManager.Stub { Log.v(TAG, "Want this data dir: " + dataPath); //invoke installer to do the actual installation if (mInstaller != null) { - int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, + int ret = mInstaller.install(pkgName, useEncryptedFSDir, pkg.applicationInfo.uid, pkg.applicationInfo.uid); if(ret < 0) { // Error from installer @@ -6209,8 +6234,9 @@ class PackageManagerService extends IPackageManager.Stub { deletedPs = mSettings.mPackages.get(packageName); } if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { + boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); if (mInstaller != null) { - int retCode = mInstaller.remove(packageName); + int retCode = mInstaller.remove(packageName, useEncryptedFSDir); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data or cache directory for package: " + packageName + ", retcode=" + retCode); @@ -6451,6 +6477,7 @@ class PackageManagerService extends IPackageManager.Stub { p = ps.pkg; } } + boolean useEncryptedFSDir = false; if(!dataOnly) { //need to check this only for fully installed applications @@ -6463,9 +6490,10 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Package " + packageName + " has no applicationInfo."); return false; } + useEncryptedFSDir = useEncryptedFilesystemForPackage(p); } if (mInstaller != null) { - int retCode = mInstaller.clearUserData(packageName); + int retCode = mInstaller.clearUserData(packageName, useEncryptedFSDir); if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName); @@ -6516,8 +6544,9 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Package " + packageName + " has no applicationInfo."); return false; } + boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); if (mInstaller != null) { - int retCode = mInstaller.deleteCacheFiles(packageName); + int retCode = mInstaller.deleteCacheFiles(packageName, useEncryptedFSDir); if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName); @@ -6579,9 +6608,10 @@ class PackageManagerService extends IPackageManager.Stub { } publicSrcDir = isForwardLocked(p) ? applicationInfo.publicSourceDir : null; } + boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); if (mInstaller != null) { int res = mInstaller.getSizeInfo(packageName, p.mPath, - publicSrcDir, pStats); + publicSrcDir, pStats, useEncryptedFSDir); if (res < 0) { return false; } else { @@ -7092,6 +7122,12 @@ class PackageManagerService extends IPackageManager.Stub { pw.print(" supportsScreens=["); boolean first = true; if ((ps.pkg.applicationInfo.flags & + ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) { + if (!first) pw.print(", "); + first = false; + pw.print("small"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) { if (!first) pw.print(", "); first = false; @@ -7104,10 +7140,10 @@ class PackageManagerService extends IPackageManager.Stub { pw.print("large"); } if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) { + ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { if (!first) pw.print(", "); first = false; - pw.print("small"); + pw.print("xlarge"); } if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { @@ -7631,7 +7667,8 @@ class PackageManagerService extends IPackageManager.Stub { this.pkgFlags = pkgFlags & ( ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_FORWARD_LOCK | - ApplicationInfo.FLAG_EXTERNAL_STORAGE); + ApplicationInfo.FLAG_EXTERNAL_STORAGE | + ApplicationInfo.FLAG_NEVER_ENCRYPT); } } @@ -7898,11 +7935,17 @@ class PackageManagerService extends IPackageManager.Stub { File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); // TODO(oam): This secure dir creation needs to be moved somewhere else (later) + File systemSecureDir = new File(dataDir, "secure/system"); systemDir.mkdirs(); + systemSecureDir.mkdirs(); FileUtils.setPermissions(systemDir.toString(), FileUtils.S_IRWXU|FileUtils.S_IRWXG |FileUtils.S_IROTH|FileUtils.S_IXOTH, -1, -1); + FileUtils.setPermissions(systemSecureDir.toString(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG + |FileUtils.S_IROTH|FileUtils.S_IXOTH, + -1, -1); mSettingsFilename = new File(systemDir, "packages.xml"); mBackupSettingsFilename = new File(systemDir, "packages-backup.xml"); mPackageListFilename = new File(systemDir, "packages.list"); diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index be5cc7b..a1c08fd 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -4900,8 +4900,12 @@ public class WindowManagerService extends IWindowManager.Stub mScreenLayout = Configuration.SCREENLAYOUT_SIZE_SMALL | Configuration.SCREENLAYOUT_LONG_NO; } else { - // Is this a large screen? - if (longSize > 640 && shortSize >= 480) { + // What size is this screen screen? + if (longSize >= 800 && shortSize >= 600) { + // SVGA or larger screens at medium density are the point + // at which we consider it to be an extra large screen. + mScreenLayout = Configuration.SCREENLAYOUT_SIZE_XLARGE; + } else if (longSize >= 640 && shortSize >= 480) { // VGA or larger screens at medium density are the point // at which we consider it to be a large screen. mScreenLayout = Configuration.SCREENLAYOUT_SIZE_LARGE; diff --git a/services/java/com/android/server/location/GeocoderProxy.java b/services/java/com/android/server/location/GeocoderProxy.java new file mode 100644 index 0000000..3c05da2 --- /dev/null +++ b/services/java/com/android/server/location/GeocoderProxy.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2010 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.server.location; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.location.Address; +import android.location.GeocoderParams; +import android.location.IGeocodeProvider; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; + +import java.util.List; + +/** + * A class for proxying IGeocodeProvider implementations. + * + * {@hide} + */ +public class GeocoderProxy { + + private static final String TAG = "GeocoderProxy"; + + private final Context mContext; + private final Intent mIntent; + private final Connection mServiceConnection = new Connection(); + private IGeocodeProvider mProvider; + + public GeocoderProxy(Context context, String serviceName) { + mContext = context; + mIntent = new Intent(serviceName); + mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE); + } + + private class Connection implements ServiceConnection { + public void onServiceConnected(ComponentName className, IBinder service) { + Log.d(TAG, "onServiceConnected " + className); + synchronized (this) { + mProvider = IGeocodeProvider.Stub.asInterface(service); + } + } + + public void onServiceDisconnected(ComponentName className) { + Log.d(TAG, "onServiceDisconnected " + className); + synchronized (this) { + mProvider = null; + } + } + } + + public String getFromLocation(double latitude, double longitude, int maxResults, + GeocoderParams params, List<Address> addrs) { + IGeocodeProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + return provider.getFromLocation(latitude, longitude, maxResults, + params, addrs); + } catch (RemoteException e) { + Log.e(TAG, "getFromLocation failed", e); + } + } + return "Service not Available"; + } + + public String getFromLocationName(String locationName, + double lowerLeftLatitude, double lowerLeftLongitude, + double upperRightLatitude, double upperRightLongitude, int maxResults, + GeocoderParams params, List<Address> addrs) { + IGeocodeProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + return provider.getFromLocationName(locationName, lowerLeftLatitude, + lowerLeftLongitude, upperRightLatitude, upperRightLongitude, + maxResults, params, addrs); + } catch (RemoteException e) { + Log.e(TAG, "getFromLocationName failed", e); + } + } + return "Service not Available"; + } +} diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java new file mode 100755 index 0000000..19c9018 --- /dev/null +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -0,0 +1,1475 @@ +/* + * 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.server.location; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.location.Criteria; +import android.location.IGpsStatusListener; +import android.location.IGpsStatusProvider; +import android.location.ILocationManager; +import android.location.INetInitiatedListener; +import android.location.Location; +import android.location.LocationManager; +import android.location.LocationProvider; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.SntpClient; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.provider.Settings; +import android.util.Log; +import android.util.SparseIntArray; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.telephony.Phone; +import com.android.internal.location.GpsNetInitiatedHandler; +import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.StringBufferInputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Date; +import java.util.Properties; +import java.util.Map.Entry; +import java.util.concurrent.CountDownLatch; + +/** + * A GPS implementation of LocationProvider used by LocationManager. + * + * {@hide} + */ +public class GpsLocationProvider implements LocationProviderInterface { + + private static final String TAG = "GpsLocationProvider"; + + private static final boolean DEBUG = false; + private static final boolean VERBOSE = false; + + // these need to match GpsPositionMode enum in gps.h + private static final int GPS_POSITION_MODE_STANDALONE = 0; + private static final int GPS_POSITION_MODE_MS_BASED = 1; + private static final int GPS_POSITION_MODE_MS_ASSISTED = 2; + + // these need to match GpsPositionRecurrence enum in gps.h + private static final int GPS_POSITION_RECURRENCE_PERIODIC = 0; + private static final int GPS_POSITION_RECURRENCE_SINGLE = 1; + + // 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 GpsApgsStatusValue defines in gps.h + /** AGPS status event values. */ + private static final int GPS_REQUEST_AGPS_DATA_CONN = 1; + private static final int GPS_RELEASE_AGPS_DATA_CONN = 2; + private static final int GPS_AGPS_DATA_CONNECTED = 3; + private static final int GPS_AGPS_DATA_CONN_DONE = 4; + private static final int GPS_AGPS_DATA_CONN_FAILED = 5; + + // 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 gps.h + 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; + + // The GPS_CAPABILITY_* flags must match the values in gps.h + private static final int GPS_CAPABILITY_SCHEDULING = 0x0000001; + private static final int GPS_CAPABILITY_MSB = 0x0000002; + private static final int GPS_CAPABILITY_MSA = 0x0000004; + private static final int GPS_CAPABILITY_SINGLE_SHOT = 0x0000008; + + + // these need to match AGpsType enum in gps.h + private static final int AGPS_TYPE_SUPL = 1; + private static final int AGPS_TYPE_C2K = 2; + + // for mAGpsDataConnectionState + private static final int AGPS_DATA_CONNECTION_CLOSED = 0; + private static final int AGPS_DATA_CONNECTION_OPENING = 1; + private static final int AGPS_DATA_CONNECTION_OPEN = 2; + + // Handler messages + private static final int CHECK_LOCATION = 1; + private static final int ENABLE = 2; + private static final int ENABLE_TRACKING = 3; + private static final int UPDATE_NETWORK_STATE = 4; + private static final int INJECT_NTP_TIME = 5; + private static final int DOWNLOAD_XTRA_DATA = 6; + private static final int UPDATE_LOCATION = 7; + private static final int ADD_LISTENER = 8; + private static final int REMOVE_LISTENER = 9; + + private static final String PROPERTIES_FILE = "/etc/gps.conf"; + + private int mLocationFlags = LOCATION_INVALID; + + // current status + private int mStatus = LocationProvider.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; + + // stop trying if we do not receive a fix within 60 seconds + private static final int NO_FIX_TIMEOUT = 60 * 1000; + + // true if we are enabled + private volatile boolean mEnabled; + + // true if we have network connectivity + private boolean mNetworkAvailable; + + // flags to trigger NTP or XTRA data download when network becomes available + // initialized to true so we do NTP and XTRA when the network comes up after booting + private boolean mInjectNtpTimePending = true; + private boolean mDownloadXtraDataPending = true; + + // true if GPS is navigating + private boolean mNavigating; + + // true if GPS engine is on + private boolean mEngineOn; + + // requested frequency of fixes, in milliseconds + private int mFixInterval = 1000; + + // true if we started navigation + private boolean mStarted; + + // capabilities of the GPS engine + private int mEngineCapabilities; + + // 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; + + private int mPositionMode; + + // properties loaded from PROPERTIES_FILE + private Properties mProperties; + private String mNtpServer; + private String mSuplServerHost; + private int mSuplServerPort; + private String mC2KServerHost; + private int mC2KServerPort; + + private final Context mContext; + private final ILocationManager mLocationManager; + private Location mLocation = new Location(LocationManager.GPS_PROVIDER); + private Bundle mLocationExtras = new Bundle(); + private ArrayList<Listener> mListeners = new ArrayList<Listener>(); + + // GpsLocationProvider's handler thread + private final Thread mThread; + // Handler for processing events in mThread. + private Handler mHandler; + // Used to signal when our main thread has initialized everything + private final CountDownLatch mInitializedLatch = new CountDownLatch(1); + // Thread for receiving events from the native code + private Thread mEventThread; + + private String mAGpsApn; + private int mAGpsDataConnectionState; + private final ConnectivityManager mConnMgr; + private final GpsNetInitiatedHandler mNIHandler; + + // Wakelocks + private final static String WAKELOCK_KEY = "GpsLocationProvider"; + private final PowerManager.WakeLock mWakeLock; + // bitfield of pending messages to our Handler + // used only for messages that cannot have multiple instances queued + private int mPendingMessageBits; + // separate counter for ADD_LISTENER and REMOVE_LISTENER messages, + // which might have multiple instances queued + private int mPendingListenerMessages; + + // Alarms + private final static String ALARM_WAKEUP = "com.android.internal.location.ALARM_WAKEUP"; + private final static String ALARM_TIMEOUT = "com.android.internal.location.ALARM_TIMEOUT"; + private final AlarmManager mAlarmManager; + private final PendingIntent mWakeupIntent; + private final PendingIntent mTimeoutIntent; + + private final IBatteryStats mBatteryStats; + private final SparseIntArray mClientUids = new SparseIntArray(); + + // 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; + + // to avoid injecting bad NTP time, we reject any time fixes that differ from system time + // by more than 5 minutes. + private static final long MAX_NTP_SYSTEM_TIME_OFFSET = 5*60*1000; + + private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() { + 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())) { + l = test; + } + } + + if (l != null) { + mListeners.remove(l); + binder.unlinkToDeath(l, 0); + } + } + } + }; + + public IGpsStatusProvider getGpsStatusProvider() { + return mGpsStatusProvider; + } + + private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (action.equals(ALARM_WAKEUP)) { + if (DEBUG) Log.d(TAG, "ALARM_WAKEUP"); + startNavigating(); + } else if (action.equals(ALARM_TIMEOUT)) { + if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT"); + hibernate(); + } + } + }; + + public static boolean isSupported() { + return native_is_supported(); + } + + public GpsLocationProvider(Context context, ILocationManager locationManager) { + mContext = context; + mLocationManager = locationManager; + mNIHandler = new GpsNetInitiatedHandler(context); + + mLocation.setExtras(mLocationExtras); + + // Create a wake lock + PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); + mWakeLock.setReferenceCounted(false); + + mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0); + mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0); + + mConnMgr = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + + // Battery statistics service to be notified when GPS turns on or off + mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo")); + + mProperties = new Properties(); + try { + File file = new File(PROPERTIES_FILE); + FileInputStream stream = new FileInputStream(file); + mProperties.load(stream); + stream.close(); + mNtpServer = mProperties.getProperty("NTP_SERVER", null); + + mSuplServerHost = mProperties.getProperty("SUPL_HOST"); + String portString = mProperties.getProperty("SUPL_PORT"); + if (mSuplServerHost != null && portString != null) { + try { + mSuplServerPort = Integer.parseInt(portString); + } catch (NumberFormatException e) { + Log.e(TAG, "unable to parse SUPL_PORT: " + portString); + } + } + + mC2KServerHost = mProperties.getProperty("C2K_HOST"); + portString = mProperties.getProperty("C2K_PORT"); + if (mC2KServerHost != null && portString != null) { + try { + mC2KServerPort = Integer.parseInt(portString); + } catch (NumberFormatException e) { + Log.e(TAG, "unable to parse C2K_PORT: " + portString); + } + } + } catch (IOException e) { + Log.w(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE); + } + + // wait until we are fully initialized before returning + mThread = new GpsLocationProviderThread(); + mThread.start(); + while (true) { + try { + mInitializedLatch.await(); + break; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + private void initialize() { + // register our receiver on our thread rather than the main thread + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(ALARM_WAKEUP); + intentFilter.addAction(ALARM_TIMEOUT); + mContext.registerReceiver(mBroadcastReciever, intentFilter); + } + + /** + * Returns the name of this provider. + */ + public String getName() { + return LocationManager.GPS_PROVIDER; + } + + /** + * Returns true if the provider requires access to a + * data network (e.g., the Internet), false otherwise. + */ + public boolean requiresNetwork() { + return true; + } + + public void updateNetworkState(int state, NetworkInfo info) { + sendMessage(UPDATE_NETWORK_STATE, state, info); + } + + private void handleUpdateNetworkState(int state, NetworkInfo info) { + mNetworkAvailable = (state == LocationProvider.AVAILABLE); + + if (DEBUG) { + Log.d(TAG, "updateNetworkState " + (mNetworkAvailable ? "available" : "unavailable") + + " info: " + info); + } + + if (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE_SUPL + && mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) { + String apnName = info.getExtraInfo(); + if (mNetworkAvailable && apnName != null && apnName.length() > 0) { + mAGpsApn = apnName; + if (DEBUG) Log.d(TAG, "call native_agps_data_conn_open"); + native_agps_data_conn_open(apnName); + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN; + } else { + if (DEBUG) Log.d(TAG, "call native_agps_data_conn_failed"); + mAGpsApn = null; + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED; + native_agps_data_conn_failed(); + } + } + + if (mNetworkAvailable) { + if (mInjectNtpTimePending) { + sendMessage(INJECT_NTP_TIME, 0, null); + } + if (mDownloadXtraDataPending) { + sendMessage(DOWNLOAD_XTRA_DATA, 0, null); + } + } + } + + private void handleInjectNtpTime() { + if (!mNetworkAvailable) { + // try again when network is up + mInjectNtpTimePending = true; + return; + } + mInjectNtpTimePending = false; + + SntpClient client = new SntpClient(); + long delay; + + if (client.requestTime(mNtpServer, 10000)) { + long time = client.getNtpTime(); + long timeReference = client.getNtpTimeReference(); + int certainty = (int)(client.getRoundTripTime()/2); + long now = System.currentTimeMillis(); + + Log.d(TAG, "NTP server returned: " + + time + " (" + new Date(time) + + ") reference: " + timeReference + + " certainty: " + certainty + + " system time offset: " + (time - now)); + + native_inject_time(time, timeReference, certainty); + delay = NTP_INTERVAL; + } else { + if (DEBUG) Log.d(TAG, "requestTime failed"); + delay = RETRY_INTERVAL; + } + + // send delayed message for next NTP injection + // since this is delayed and not urgent we do not hold a wake lock here + mHandler.removeMessages(INJECT_NTP_TIME); + mHandler.sendMessageDelayed(Message.obtain(mHandler, INJECT_NTP_TIME), delay); + } + + private void handleDownloadXtraData() { + if (!mDownloadXtraDataPending) { + // try again when network is up + mDownloadXtraDataPending = true; + return; + } + mDownloadXtraDataPending = false; + + + GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mContext, mProperties); + byte[] data = xtraDownloader.downloadXtraData(); + if (data != null) { + if (DEBUG) { + Log.d(TAG, "calling native_inject_xtra_data"); + } + native_inject_xtra_data(data, data.length); + } else { + // try again later + // since this is delayed and not urgent we do not hold a wake lock here + mHandler.removeMessages(DOWNLOAD_XTRA_DATA); + mHandler.sendMessageDelayed(Message.obtain(mHandler, DOWNLOAD_XTRA_DATA), RETRY_INTERVAL); + } + } + + /** + * This is called to inform us when another location provider returns a location. + * Someday we might use this for network location injection to aid the GPS + */ + public void updateLocation(Location location) { + sendMessage(UPDATE_LOCATION, 0, location); + } + + private void handleUpdateLocation(Location location) { + if (location.hasAccuracy()) { + native_inject_location(location.getLatitude(), location.getLongitude(), + location.getAccuracy()); + } + } + + /** + * Returns true if the provider requires access to a + * satellite-based positioning system (e.g., GPS), false + * otherwise. + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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_*. + */ + 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_*. + */ + public int getAccuracy() { + return Criteria.ACCURACY_FINE; + } + + /** + * Enables this provider. When enabled, calls to getStatus() + * must be handled. Hardware may be started up + * when the provider is enabled. + */ + public void enable() { + synchronized (mHandler) { + sendMessage(ENABLE, 1, null); + } + } + + private void handleEnable() { + if (DEBUG) Log.d(TAG, "handleEnable"); + if (mEnabled) return; + mEnabled = native_init(); + + if (mEnabled) { + if (mSuplServerHost != null) { + native_set_agps_server(AGPS_TYPE_SUPL, mSuplServerHost, mSuplServerPort); + } + if (mC2KServerHost != null) { + native_set_agps_server(AGPS_TYPE_C2K, mC2KServerHost, mC2KServerPort); + } + + // run event listener thread while we are enabled + mEventThread = new GpsEventThread(); + mEventThread.start(); + } else { + Log.w(TAG, "Failed to enable location provider"); + } + } + + /** + * Disables this provider. When disabled, calls to getStatus() + * need not be handled. Hardware may be shut + * down while the provider is disabled. + */ + public void disable() { + synchronized (mHandler) { + sendMessage(ENABLE, 0, null); + } + } + + private void handleDisable() { + if (DEBUG) Log.d(TAG, "handleDisable"); + if (!mEnabled) return; + + 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; + } + + // do this before releasing wakelock + native_cleanup(); + + // The GpsEventThread does not wait for the GPS to shutdown + // so we need to report the GPS_STATUS_ENGINE_OFF event here + if (mNavigating) { + reportStatus(GPS_STATUS_SESSION_END); + } + if (mEngineOn) { + reportStatus(GPS_STATUS_ENGINE_OFF); + } + } + + public boolean isEnabled() { + return mEnabled; + } + + 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(); + } + } + + public long getStatusUpdateTime() { + return mStatusUpdateTime; + } + + public void enableLocationTracking(boolean enable) { + synchronized (mHandler) { + sendMessage(ENABLE_TRACKING, (enable ? 1 : 0), null); + } + } + + private void handleEnableLocationTracking(boolean enable) { + if (enable) { + mTTFF = 0; + mLastFixTime = 0; + startNavigating(); + } else { + if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) { + mAlarmManager.cancel(mWakeupIntent); + mAlarmManager.cancel(mTimeoutIntent); + } + stopNavigating(); + } + } + + public void setMinTime(long minTime) { + if (DEBUG) Log.d(TAG, "setMinTime " + minTime); + + if (minTime >= 0) { + mFixInterval = (int)minTime; + + if (mStarted && hasCapability(GPS_CAPABILITY_SCHEDULING)) { + if (!native_set_position_mode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, + mFixInterval, 0, 0)) { + Log.e(TAG, "set_position_mode failed in setMinTime()"); + } + } + } + } + + public String getInternalState() { + return native_get_internal_state(); + } + + private final class Listener implements IBinder.DeathRecipient { + final IGpsStatusListener mListener; + + int mSensors = 0; + + Listener(IGpsStatusListener listener) { + mListener = listener; + } + + public void binderDied() { + if (DEBUG) Log.d(TAG, "GPS status listener died"); + + synchronized(mListeners) { + mListeners.remove(this); + } + if (mListener != null) { + mListener.asBinder().unlinkToDeath(this, 0); + } + } + } + + public void addListener(int uid) { + synchronized (mWakeLock) { + mPendingListenerMessages++; + mWakeLock.acquire(); + Message m = Message.obtain(mHandler, ADD_LISTENER); + m.arg1 = uid; + mHandler.sendMessage(m); + } + } + + private void handleAddListener(int uid) { + synchronized(mListeners) { + if (mClientUids.indexOfKey(uid) >= 0) { + // Shouldn't be here -- already have this uid. + Log.w(TAG, "Duplicate add listener for uid " + uid); + return; + } + mClientUids.put(uid, 0); + if (mNavigating) { + try { + mBatteryStats.noteStartGps(uid); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in addListener"); + } + } + } + } + + public void removeListener(int uid) { + synchronized (mWakeLock) { + mPendingListenerMessages++; + mWakeLock.acquire(); + Message m = Message.obtain(mHandler, REMOVE_LISTENER); + m.arg1 = uid; + mHandler.sendMessage(m); + } + } + + private void handleRemoveListener(int uid) { + synchronized(mListeners) { + if (mClientUids.indexOfKey(uid) < 0) { + // Shouldn't be here -- don't have this uid. + Log.w(TAG, "Unneeded remove listener for uid " + uid); + return; + } + mClientUids.delete(uid); + if (mNavigating) { + try { + mBatteryStats.noteStopGps(uid); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in removeListener"); + } + } + } + } + + public boolean sendExtraCommand(String command, Bundle extras) { + + long identity = Binder.clearCallingIdentity(); + boolean result = false; + + if ("delete_aiding_data".equals(command)) { + result = deleteAidingData(extras); + } else if ("force_time_injection".equals(command)) { + sendMessage(INJECT_NTP_TIME, 0, null); + result = true; + } else if ("force_xtra_injection".equals(command)) { + if (native_supports_xtra()) { + xtraDownloadRequest(); + result = true; + } + } else { + Log.w(TAG, "sendExtraCommand: unknown command " + command); + } + + Binder.restoreCallingIdentity(identity); + return result; + } + + 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; + } + + private void startNavigating() { + if (!mStarted) { + if (DEBUG) Log.d(TAG, "startNavigating"); + mStarted = true; + if (hasCapability(GPS_CAPABILITY_MSB) && + Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ASSISTED_GPS_ENABLED, 1) != 0) { + mPositionMode = GPS_POSITION_MODE_MS_BASED; + } else { + mPositionMode = GPS_POSITION_MODE_STANDALONE; + } + + int interval = (hasCapability(GPS_CAPABILITY_SCHEDULING) ? mFixInterval : 1000); + if (!native_set_position_mode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, + interval, 0, 0)) { + mStarted = false; + Log.e(TAG, "set_position_mode failed in startNavigating()"); + return; + } + if (!native_start()) { + mStarted = false; + Log.e(TAG, "native_start failed in startNavigating()"); + return; + } + + // reset SV count to zero + updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0); + mFixRequestTime = System.currentTimeMillis(); + if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) { + // set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT + // and our fix interval is not short + if (mFixInterval >= NO_FIX_TIMEOUT) { + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, mTimeoutIntent); + } + } + } + } + + private void stopNavigating() { + if (DEBUG) Log.d(TAG, "stopNavigating"); + if (mStarted) { + mStarted = false; + native_stop(); + mTTFF = 0; + mLastFixTime = 0; + mLocationFlags = LOCATION_INVALID; + + // reset SV count to zero + updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0); + } + } + + private void hibernate() { + // stop GPS until our next fix interval arrives + stopNavigating(); + mAlarmManager.cancel(mTimeoutIntent); + mAlarmManager.cancel(mWakeupIntent); + long now = SystemClock.elapsedRealtime(); + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + mFixInterval, mWakeupIntent); + } + + private boolean hasCapability(int capability) { + return ((mEngineCapabilities & capability) != 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 (VERBOSE) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude + + " timestamp: " + timestamp); + + 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(); + } + + try { + mLocationManager.reportLocation(mLocation, false); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling reportLocation"); + } + } + + mLastFixTime = System.currentTimeMillis(); + // report time to first fix + if (mTTFF == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { + mTTFF = (int)(mLastFixTime - mFixRequestTime); + if (DEBUG) 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--; + } + } + } + } + + if (mStarted && mStatus != LocationProvider.AVAILABLE) { + // we want to time out if we do not receive a fix + // within the time out and we are requesting infrequent fixes + if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mFixInterval < NO_FIX_TIMEOUT) { + mAlarmManager.cancel(mTimeoutIntent); + } + + // send an intent to notify that the GPS is receiving fixes. + Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION); + intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, true); + mContext.sendBroadcast(intent); + updateStatus(LocationProvider.AVAILABLE, mSvCount); + } + + if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mFixInterval > 1000) { + if (DEBUG) Log.d(TAG, "got fix, hibernating"); + hibernate(); + } + } + + /** + * called from native code to update our status + */ + private void reportStatus(int status) { + if (VERBOSE) Log.v(TAG, "reportStatus status: " + status); + + synchronized(mListeners) { + boolean wasNavigating = mNavigating; + + switch (status) { + case GPS_STATUS_SESSION_BEGIN: + mNavigating = true; + mEngineOn = true; + break; + case GPS_STATUS_SESSION_END: + mNavigating = false; + break; + case GPS_STATUS_ENGINE_ON: + mEngineOn = true; + break; + case GPS_STATUS_ENGINE_OFF: + mEngineOn = false; + mNavigating = false; + break; + } + + if (wasNavigating != mNavigating) { + 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--; + } + } + + try { + // update battery stats + for (int i=mClientUids.size() - 1; i >= 0; i--) { + int uid = mClientUids.keyAt(i); + if (mNavigating) { + mBatteryStats.noteStartGps(uid); + } else { + mBatteryStats.noteStopGps(uid); + } + } + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportStatus"); + } + + // send an intent to notify that the GPS has been enabled or disabled. + Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION); + intent.putExtra(LocationManager.EXTRA_GPS_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 (VERBOSE) { + 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++) { + 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 == LocationProvider.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(LocationManager.GPS_FIX_CHANGE_ACTION); + intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, false); + mContext.sendBroadcast(intent); + updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, mSvCount); + } + } + + /** + * called from native code to update AGPS status + */ + private void reportAGpsStatus(int type, int status) { + switch (status) { + case GPS_REQUEST_AGPS_DATA_CONN: + int result = mConnMgr.startUsingNetworkFeature( + ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL); + if (result == Phone.APN_ALREADY_ACTIVE) { + if (mAGpsApn != null) { + native_agps_data_conn_open(mAGpsApn); + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN; + } else { + Log.e(TAG, "mAGpsApn not set when receiving Phone.APN_ALREADY_ACTIVE"); + native_agps_data_conn_failed(); + } + } else if (result == Phone.APN_REQUEST_STARTED) { + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING; + } else { + native_agps_data_conn_failed(); + } + break; + case GPS_RELEASE_AGPS_DATA_CONN: + if (mAGpsDataConnectionState != AGPS_DATA_CONNECTION_CLOSED) { + mConnMgr.stopUsingNetworkFeature( + ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL); + native_agps_data_conn_closed(); + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED; + } + break; + case GPS_AGPS_DATA_CONNECTED: + // Log.d(TAG, "GPS_AGPS_DATA_CONNECTED"); + break; + case GPS_AGPS_DATA_CONN_DONE: + // Log.d(TAG, "GPS_AGPS_DATA_CONN_DONE"); + break; + case GPS_AGPS_DATA_CONN_FAILED: + // Log.d(TAG, "GPS_AGPS_DATA_CONN_FAILED"); + break; + } + } + + /** + * called from native code to report NMEA data received + */ + private void reportNmea(int index, long timestamp) { + synchronized(mListeners) { + int size = mListeners.size(); + if (size > 0) { + // don't bother creating the String if we have no listeners + int length = native_read_nmea(index, mNmeaBuffer, mNmeaBuffer.length); + String nmea = new String(mNmeaBuffer, 0, length); + + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + listener.mListener.onNmeaReceived(timestamp, nmea); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportNmea"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + } + } + } + + /** + * called from native code to inform us what the GPS engine capabilities are + */ + private void setEngineCapabilities(int capabilities) { + mEngineCapabilities = capabilities; + } + + /** + * called from native code to request XTRA data + */ + private void xtraDownloadRequest() { + if (DEBUG) Log.d(TAG, "xtraDownloadRequest"); + sendMessage(DOWNLOAD_XTRA_DATA, 0, null); + } + + //============================================================= + // NI Client support + //============================================================= + private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() { + // Sends a response for an NI reqeust to HAL. + public boolean sendNiResponse(int notificationId, int userResponse) + { + // TODO Add Permission check + + StringBuilder extrasBuf = new StringBuilder(); + + if (DEBUG) Log.d(TAG, "sendNiResponse, notifId: " + notificationId + + ", response: " + userResponse); + + native_send_ni_response(notificationId, userResponse); + + return true; + } + }; + + public INetInitiatedListener getNetInitiatedListener() { + return mNetInitiatedListener; + } + + // Called by JNI function to report an NI request. + @SuppressWarnings("deprecation") + public void reportNiNotification( + int notificationId, + int niType, + int notifyFlags, + int timeout, + int defaultResponse, + String requestorId, + String text, + int requestorIdEncoding, + int textEncoding, + String extras // Encoded extra data + ) + { + Log.i(TAG, "reportNiNotification: entered"); + Log.i(TAG, "notificationId: " + notificationId + + ", niType: " + niType + + ", notifyFlags: " + notifyFlags + + ", timeout: " + timeout + + ", defaultResponse: " + defaultResponse); + + Log.i(TAG, "requestorId: " + requestorId + + ", text: " + text + + ", requestorIdEncoding: " + requestorIdEncoding + + ", textEncoding: " + textEncoding); + + GpsNiNotification notification = new GpsNiNotification(); + + notification.notificationId = notificationId; + notification.niType = niType; + notification.needNotify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_NOTIFY) != 0; + notification.needVerify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_VERIFY) != 0; + notification.privacyOverride = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_PRIVACY_OVERRIDE) != 0; + notification.timeout = timeout; + notification.defaultResponse = defaultResponse; + notification.requestorId = requestorId; + notification.text = text; + notification.requestorIdEncoding = requestorIdEncoding; + notification.textEncoding = textEncoding; + + // Process extras, assuming the format is + // one of more lines of "key = value" + Bundle bundle = new Bundle(); + + if (extras == null) extras = ""; + Properties extraProp = new Properties(); + + try { + extraProp.load(new StringBufferInputStream(extras)); + } + catch (IOException e) + { + Log.e(TAG, "reportNiNotification cannot parse extras data: " + extras); + } + + for (Entry<Object, Object> ent : extraProp.entrySet()) + { + bundle.putString((String) ent.getKey(), (String) ent.getValue()); + } + + notification.extras = bundle; + + mNIHandler.handleNiNotification(notification); + } + + // this thread is used to receive events from the native code. + // native_wait_for_event() will callback to us via reportLocation(), reportStatus(), etc. + // this is necessary because native code cannot call Java on a thread that the JVM does + // not know about. + private final class GpsEventThread extends Thread { + + public GpsEventThread() { + super("GpsEventThread"); + } + + public void run() { + if (DEBUG) Log.d(TAG, "GpsEventThread starting"); + // Exit as soon as disable() is called instead of waiting for the GPS to stop. + while (mEnabled) { + // this will wait for an event from the GPS, + // which will be reported via reportLocation or reportStatus + native_wait_for_event(); + } + if (DEBUG) Log.d(TAG, "GpsEventThread exiting"); + } + } + + private void sendMessage(int message, int arg, Object obj) { + // hold a wake lock while messages are pending + synchronized (mWakeLock) { + mPendingMessageBits |= (1 << message); + mWakeLock.acquire(); + mHandler.removeMessages(message); + Message m = Message.obtain(mHandler, message); + m.arg1 = arg; + m.obj = obj; + mHandler.sendMessage(m); + } + } + + private final class ProviderHandler extends Handler { + @Override + public void handleMessage(Message msg) + { + int message = msg.what; + switch (message) { + case ENABLE: + if (msg.arg1 == 1) { + handleEnable(); + } else { + handleDisable(); + } + break; + case ENABLE_TRACKING: + handleEnableLocationTracking(msg.arg1 == 1); + break; + case UPDATE_NETWORK_STATE: + handleUpdateNetworkState(msg.arg1, (NetworkInfo)msg.obj); + break; + case INJECT_NTP_TIME: + handleInjectNtpTime(); + break; + case DOWNLOAD_XTRA_DATA: + if (native_supports_xtra()) { + handleDownloadXtraData(); + } + break; + case UPDATE_LOCATION: + handleUpdateLocation((Location)msg.obj); + break; + case ADD_LISTENER: + handleAddListener(msg.arg1); + break; + case REMOVE_LISTENER: + handleRemoveListener(msg.arg1); + break; + } + // release wake lock if no messages are pending + synchronized (mWakeLock) { + mPendingMessageBits &= ~(1 << message); + if (message == ADD_LISTENER || message == REMOVE_LISTENER) { + mPendingListenerMessages--; + } + if (mPendingMessageBits == 0 && mPendingListenerMessages == 0) { + mWakeLock.release(); + } + } + } + }; + + private final class GpsLocationProviderThread extends Thread { + + public GpsLocationProviderThread() { + super("GpsLocationProvider"); + } + + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + initialize(); + Looper.prepare(); + mHandler = new ProviderHandler(); + // signal when we are initialized and ready to go + mInitializedLatch.countDown(); + Looper.loop(); + } + } + + // 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; + // preallocated to avoid memory allocation in reportNmea() + private byte[] mNmeaBuffer = new byte[120]; + + 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_set_position_mode(int mode, int recurrence, int min_interval, + int preferred_accuracy, int preferred_time); + private native boolean native_start(); + private native boolean native_stop(); + 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 int native_read_nmea(int index, byte[] buffer, int bufferSize); + private native void native_inject_location(double latitude, double longitude, float accuracy); + + // XTRA Support + 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); + + // DEBUG Support + private native String native_get_internal_state(); + + // AGPS Support + private native void native_agps_data_conn_open(String apn); + private native void native_agps_data_conn_closed(); + private native void native_agps_data_conn_failed(); + private native void native_set_agps_server(int type, String hostname, int port); + + // Network-initiated (NI) Support + private native void native_send_ni_response(int notificationId, int userResponse); +} diff --git a/services/java/com/android/server/location/GpsXtraDownloader.java b/services/java/com/android/server/location/GpsXtraDownloader.java new file mode 100644 index 0000000..bc96980 --- /dev/null +++ b/services/java/com/android/server/location/GpsXtraDownloader.java @@ -0,0 +1,171 @@ +/* + * 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.server.location; + +import android.content.Context; +import android.net.Proxy; +import android.net.http.AndroidHttpClient; +import android.util.Config; +import android.util.Log; + +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 java.util.Random; + +/** + * 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; + + 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"); + return; + } 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; + + // randomize first server + Random random = new Random(); + mNextServerIndex = random.nextInt(count); + } + } + + 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) { + if (Config.LOGD) Log.d(TAG, "Downloading XTRA data from " + url); + + 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. + if (Config.LOGD) 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) { + if (Config.LOGD) Log.d(TAG, "error " + e); + } finally { + if (client != null) { + client.close(); + } + } + return null; + } + +} diff --git a/services/java/com/android/server/location/LocationProviderInterface.java b/services/java/com/android/server/location/LocationProviderInterface.java new file mode 100644 index 0000000..a472143 --- /dev/null +++ b/services/java/com/android/server/location/LocationProviderInterface.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010 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.server.location; + +import android.location.Location; +import android.net.NetworkInfo; +import android.os.Bundle; + +/** + * Location Manager's interface for location providers. + * + * {@hide} + */ +public interface LocationProviderInterface { + String getName(); + boolean requiresNetwork(); + boolean requiresSatellite(); + boolean requiresCell(); + boolean hasMonetaryCost(); + boolean supportsAltitude(); + boolean supportsSpeed(); + boolean supportsBearing(); + int getPowerRequirement(); + int getAccuracy(); + boolean isEnabled(); + void enable(); + void disable(); + int getStatus(Bundle extras); + long getStatusUpdateTime(); + void enableLocationTracking(boolean enable); + String getInternalState(); + void setMinTime(long minTime); + void updateNetworkState(int state, NetworkInfo info); + void updateLocation(Location location); + boolean sendExtraCommand(String command, Bundle extras); + void addListener(int uid); + void removeListener(int uid); +} diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java new file mode 100644 index 0000000..3e118f9 --- /dev/null +++ b/services/java/com/android/server/location/LocationProviderProxy.java @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2009 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.server.location; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.location.ILocationProvider; +import android.location.Location; +import android.net.NetworkInfo; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; + +import com.android.internal.location.DummyLocationProvider; + +/** + * A class for proxying location providers implemented as services. + * + * {@hide} + */ +public class LocationProviderProxy implements LocationProviderInterface { + + private static final String TAG = "LocationProviderProxy"; + + private final Context mContext; + private final String mName; + private ILocationProvider mProvider; + private Handler mHandler; + private final Connection mServiceConnection = new Connection(); + + // cached values set by the location manager + private boolean mLocationTracking = false; + private boolean mEnabled = false; + private long mMinTime = -1; + private int mNetworkState; + private NetworkInfo mNetworkInfo; + + // for caching requiresNetwork, requiresSatellite, etc. + private DummyLocationProvider mCachedAttributes; + + // constructor for proxying location providers implemented in a separate service + public LocationProviderProxy(Context context, String name, String serviceName, + Handler handler) { + mContext = context; + mName = name; + mHandler = handler; + mContext.bindService(new Intent(serviceName), mServiceConnection, Context.BIND_AUTO_CREATE); + } + + private class Connection implements ServiceConnection { + public void onServiceConnected(ComponentName className, IBinder service) { + Log.d(TAG, "LocationProviderProxy.onServiceConnected " + className); + synchronized (this) { + mProvider = ILocationProvider.Stub.asInterface(service); + if (mProvider != null) { + mHandler.post(mServiceConnectedTask); + } + } + } + + public void onServiceDisconnected(ComponentName className) { + Log.d(TAG, "LocationProviderProxy.onServiceDisconnected " + className); + synchronized (this) { + mProvider = null; + } + } + } + + private Runnable mServiceConnectedTask = new Runnable() { + public void run() { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + if (provider == null) { + return; + } + } + + if (mCachedAttributes == null) { + try { + mCachedAttributes = new DummyLocationProvider(mName); + mCachedAttributes.setRequiresNetwork(provider.requiresNetwork()); + mCachedAttributes.setRequiresSatellite(provider.requiresSatellite()); + mCachedAttributes.setRequiresCell(provider.requiresCell()); + mCachedAttributes.setHasMonetaryCost(provider.hasMonetaryCost()); + mCachedAttributes.setSupportsAltitude(provider.supportsAltitude()); + mCachedAttributes.setSupportsSpeed(provider.supportsSpeed()); + mCachedAttributes.setSupportsBearing(provider.supportsBearing()); + mCachedAttributes.setPowerRequirement(provider.getPowerRequirement()); + mCachedAttributes.setAccuracy(provider.getAccuracy()); + } catch (RemoteException e) { + mCachedAttributes = null; + } + } + + // resend previous values from the location manager if the service has restarted + try { + if (mEnabled) { + provider.enable(); + } + if (mLocationTracking) { + provider.enableLocationTracking(true); + } + if (mMinTime >= 0) { + provider.setMinTime(mMinTime); + } + if (mNetworkInfo != null) { + provider.updateNetworkState(mNetworkState, mNetworkInfo); + } + } catch (RemoteException e) { + } + } + }; + + public String getName() { + return mName; + } + + public boolean requiresNetwork() { + if (mCachedAttributes != null) { + return mCachedAttributes.requiresNetwork(); + } else { + return false; + } + } + + public boolean requiresSatellite() { + if (mCachedAttributes != null) { + return mCachedAttributes.requiresSatellite(); + } else { + return false; + } + } + + public boolean requiresCell() { + if (mCachedAttributes != null) { + return mCachedAttributes.requiresCell(); + } else { + return false; + } + } + + public boolean hasMonetaryCost() { + if (mCachedAttributes != null) { + return mCachedAttributes.hasMonetaryCost(); + } else { + return false; + } + } + + public boolean supportsAltitude() { + if (mCachedAttributes != null) { + return mCachedAttributes.supportsAltitude(); + } else { + return false; + } + } + + public boolean supportsSpeed() { + if (mCachedAttributes != null) { + return mCachedAttributes.supportsSpeed(); + } else { + return false; + } + } + + public boolean supportsBearing() { + if (mCachedAttributes != null) { + return mCachedAttributes.supportsBearing(); + } else { + return false; + } + } + + public int getPowerRequirement() { + if (mCachedAttributes != null) { + return mCachedAttributes.getPowerRequirement(); + } else { + return -1; + } + } + + public int getAccuracy() { + if (mCachedAttributes != null) { + return mCachedAttributes.getAccuracy(); + } else { + return -1; + } + } + + public void enable() { + mEnabled = true; + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.enable(); + } catch (RemoteException e) { + } + } + } + + public void disable() { + mEnabled = false; + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.disable(); + } catch (RemoteException e) { + } + } + } + + public boolean isEnabled() { + return mEnabled; + } + + public int getStatus(Bundle extras) { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + return provider.getStatus(extras); + } catch (RemoteException e) { + } + } + return 0; + } + + public long getStatusUpdateTime() { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + return provider.getStatusUpdateTime(); + } catch (RemoteException e) { + } + } + return 0; + } + + public String getInternalState() { + try { + return mProvider.getInternalState(); + } catch (RemoteException e) { + Log.e(TAG, "getInternalState failed", e); + return null; + } + } + + public boolean isLocationTracking() { + return mLocationTracking; + } + + public void enableLocationTracking(boolean enable) { + mLocationTracking = enable; + if (!enable) { + mMinTime = -1; + } + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.enableLocationTracking(enable); + } catch (RemoteException e) { + } + } + } + + public long getMinTime() { + return mMinTime; + } + + public void setMinTime(long minTime) { + mMinTime = minTime; + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.setMinTime(minTime); + } catch (RemoteException e) { + } + } + } + + public void updateNetworkState(int state, NetworkInfo info) { + mNetworkState = state; + mNetworkInfo = info; + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.updateNetworkState(state, info); + } catch (RemoteException e) { + } + } + } + + public void updateLocation(Location location) { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.updateLocation(location); + } catch (RemoteException e) { + } + } + } + + public boolean sendExtraCommand(String command, Bundle extras) { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.sendExtraCommand(command, extras); + } catch (RemoteException e) { + } + } + return false; + } + + public void addListener(int uid) { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.addListener(uid); + } catch (RemoteException e) { + } + } + } + + public void removeListener(int uid) { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.removeListener(uid); + } catch (RemoteException e) { + } + } + } +} diff --git a/services/java/com/android/server/location/MockProvider.java b/services/java/com/android/server/location/MockProvider.java new file mode 100644 index 0000000..e3f3346 --- /dev/null +++ b/services/java/com/android/server/location/MockProvider.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2009 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.server.location; + +import android.location.ILocationManager; +import android.location.Location; +import android.location.LocationProvider; +import android.net.NetworkInfo; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; +import android.util.PrintWriterPrinter; + +import java.io.PrintWriter; + +/** + * A mock location provider used by LocationManagerService to implement test providers. + * + * {@hide} + */ +public class MockProvider implements LocationProviderInterface { + private final String mName; + private final ILocationManager mLocationManager; + private final boolean mRequiresNetwork; + private final boolean mRequiresSatellite; + private final boolean mRequiresCell; + private final boolean mHasMonetaryCost; + private final boolean mSupportsAltitude; + private final boolean mSupportsSpeed; + private final boolean mSupportsBearing; + private final int mPowerRequirement; + private final int mAccuracy; + private final Location mLocation; + private int mStatus; + private long mStatusUpdateTime; + private final Bundle mExtras = new Bundle(); + private boolean mHasLocation; + private boolean mHasStatus; + private boolean mEnabled; + + private static final String TAG = "MockProvider"; + + public MockProvider(String name, ILocationManager locationManager, + boolean requiresNetwork, boolean requiresSatellite, + boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, + boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) { + mName = name; + mLocationManager = locationManager; + mRequiresNetwork = requiresNetwork; + mRequiresSatellite = requiresSatellite; + mRequiresCell = requiresCell; + mHasMonetaryCost = hasMonetaryCost; + mSupportsAltitude = supportsAltitude; + mSupportsBearing = supportsBearing; + mSupportsSpeed = supportsSpeed; + mPowerRequirement = powerRequirement; + mAccuracy = accuracy; + mLocation = new Location(name); + } + + public String getName() { + return mName; + } + + public void disable() { + mEnabled = false; + } + + public void enable() { + mEnabled = true; + } + + public boolean isEnabled() { + return mEnabled; + } + + public int getStatus(Bundle extras) { + if (mHasStatus) { + extras.clear(); + extras.putAll(mExtras); + return mStatus; + } else { + return LocationProvider.AVAILABLE; + } + } + + public long getStatusUpdateTime() { + return mStatusUpdateTime; + } + + public int getAccuracy() { + return mAccuracy; + } + + public int getPowerRequirement() { + return mPowerRequirement; + } + + public boolean hasMonetaryCost() { + return mHasMonetaryCost; + } + + public boolean requiresCell() { + return mRequiresCell; + } + + public boolean requiresNetwork() { + return mRequiresNetwork; + } + + public boolean requiresSatellite() { + return mRequiresSatellite; + } + + public boolean supportsAltitude() { + return mSupportsAltitude; + } + + public boolean supportsBearing() { + return mSupportsBearing; + } + + public boolean supportsSpeed() { + return mSupportsSpeed; + } + + public void setLocation(Location l) { + mLocation.set(l); + mHasLocation = true; + try { + mLocationManager.reportLocation(mLocation, false); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling reportLocation"); + } + } + + public void clearLocation() { + mHasLocation = false; + } + + public void setStatus(int status, Bundle extras, long updateTime) { + mStatus = status; + mStatusUpdateTime = updateTime; + mExtras.clear(); + if (extras != null) { + mExtras.putAll(extras); + } + mHasStatus = true; + } + + public void clearStatus() { + mHasStatus = false; + mStatusUpdateTime = 0; + } + + public String getInternalState() { + return null; + } + + public void enableLocationTracking(boolean enable) { + } + + public void setMinTime(long minTime) { + } + + public void updateNetworkState(int state, NetworkInfo info) { + } + + public void updateLocation(Location location) { + } + + public boolean sendExtraCommand(String command, Bundle extras) { + return false; + } + + public void addListener(int uid) { + } + + public void removeListener(int uid) { + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + mName); + pw.println(prefix + "mHasLocation=" + mHasLocation); + pw.println(prefix + "mLocation:"); + mLocation.dump(new PrintWriterPrinter(pw), prefix + " "); + pw.println(prefix + "mHasStatus=" + mHasStatus); + pw.println(prefix + "mStatus=" + mStatus); + pw.println(prefix + "mStatusUpdateTime=" + mStatusUpdateTime); + pw.println(prefix + "mExtras=" + mExtras); + } +} diff --git a/services/java/com/android/server/location/PassiveProvider.java b/services/java/com/android/server/location/PassiveProvider.java new file mode 100644 index 0000000..5ed1558 --- /dev/null +++ b/services/java/com/android/server/location/PassiveProvider.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2010 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.server.location; + +import android.location.ILocationManager; +import android.location.Location; +import android.location.LocationManager; +import android.location.LocationProvider; +import android.net.NetworkInfo; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; + +/** + * A passive location provider reports locations received from other providers + * for clients that want to listen passively without actually triggering + * location updates. + * + * {@hide} + */ +public class PassiveProvider implements LocationProviderInterface { + + private static final String TAG = "PassiveProvider"; + + private final ILocationManager mLocationManager; + private boolean mTracking; + + public PassiveProvider(ILocationManager locationManager) { + mLocationManager = locationManager; + } + + public String getName() { + return LocationManager.PASSIVE_PROVIDER; + } + + public boolean requiresNetwork() { + return false; + } + + public boolean requiresSatellite() { + return false; + } + + public boolean requiresCell() { + return false; + } + + public boolean hasMonetaryCost() { + return false; + } + + public boolean supportsAltitude() { + return false; + } + + public boolean supportsSpeed() { + return false; + } + + public boolean supportsBearing() { + return false; + } + + public int getPowerRequirement() { + return -1; + } + + public int getAccuracy() { + return -1; + } + + public boolean isEnabled() { + return true; + } + + public void enable() { + } + + public void disable() { + } + + public int getStatus(Bundle extras) { + if (mTracking) { + return LocationProvider.AVAILABLE; + } else { + return LocationProvider.TEMPORARILY_UNAVAILABLE; + } + } + + public long getStatusUpdateTime() { + return -1; + } + + public String getInternalState() { + return null; + } + + public void enableLocationTracking(boolean enable) { + mTracking = enable; + } + + public void setMinTime(long minTime) { + } + + public void updateNetworkState(int state, NetworkInfo info) { + } + + public void updateLocation(Location location) { + if (mTracking) { + try { + // pass the location back to the location manager + mLocationManager.reportLocation(location, true); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling reportLocation"); + } + } + } + + public boolean sendExtraCommand(String command, Bundle extras) { + return false; + } + + public void addListener(int uid) { + } + + public void removeListener(int uid) { + } +} diff --git a/services/java/com/android/server/status/StatusBarPolicy.java b/services/java/com/android/server/status/StatusBarPolicy.java index 3b0c436..35ccfe8 100644 --- a/services/java/com/android/server/status/StatusBarPolicy.java +++ b/services/java/com/android/server/status/StatusBarPolicy.java @@ -30,6 +30,7 @@ import android.content.IntentFilter; import android.content.res.TypedArray; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; +import android.location.LocationManager; import android.media.AudioManager; import android.media.Ringtone; import android.media.RingtoneManager; @@ -62,7 +63,6 @@ import android.widget.TextView; import com.android.internal.R; import com.android.internal.app.IBatteryStats; -import com.android.internal.location.GpsLocationProvider; import com.android.internal.telephony.IccCard; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.cdma.EriInfo; @@ -390,8 +390,8 @@ public class StatusBarPolicy { action.equals(WifiManager.RSSI_CHANGED_ACTION)) { updateWifi(intent); } - else if (action.equals(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION) || - action.equals(GpsLocationProvider.GPS_FIX_CHANGE_ACTION)) { + else if (action.equals(LocationManager.GPS_ENABLED_CHANGE_ACTION) || + action.equals(LocationManager.GPS_FIX_CHANGE_ACTION)) { updateGps(intent); } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION) || @@ -536,8 +536,8 @@ public class StatusBarPolicy { filter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); filter.addAction(WifiManager.RSSI_CHANGED_ACTION); - filter.addAction(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION); - filter.addAction(GpsLocationProvider.GPS_FIX_CHANGE_ACTION); + filter.addAction(LocationManager.GPS_ENABLED_CHANGE_ACTION); + filter.addAction(LocationManager.GPS_FIX_CHANGE_ACTION); filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION); mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); @@ -1181,7 +1181,7 @@ public class StatusBarPolicy { final int ringerMode = audioManager.getRingerMode(); final boolean visible = ringerMode == AudioManager.RINGER_MODE_SILENT || ringerMode == AudioManager.RINGER_MODE_VIBRATE; - final int iconId = (ringerMode == AudioManager.RINGER_MODE_VIBRATE) + final int iconId = audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER) ? com.android.internal.R.drawable.stat_sys_ringer_vibrate : com.android.internal.R.drawable.stat_sys_ringer_silent; @@ -1292,13 +1292,13 @@ public class StatusBarPolicy { private final void updateGps(Intent intent) { final String action = intent.getAction(); - final boolean enabled = intent.getBooleanExtra(GpsLocationProvider.EXTRA_ENABLED, false); + final boolean enabled = intent.getBooleanExtra(LocationManager.EXTRA_GPS_ENABLED, false); - if (action.equals(GpsLocationProvider.GPS_FIX_CHANGE_ACTION) && enabled) { + if (action.equals(LocationManager.GPS_FIX_CHANGE_ACTION) && enabled) { // GPS is getting fixes mService.updateIcon(mGpsIcon, mGpsFixIconData, null); mService.setIconVisibility(mGpsIcon, true); - } else if (action.equals(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION) && !enabled) { + } else if (action.equals(LocationManager.GPS_ENABLED_CHANGE_ACTION) && !enabled) { // GPS is off mService.setIconVisibility(mGpsIcon, false); } else { |