blob: 1a1a17080d01629e30c0faf6b19e7cba8e507dc6 [file] [log] [blame]
Mike Lockwoode932f7f2009-04-06 10:51:26 -07001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Mike Lockwood00b74272010-03-26 10:41:48 -040017package com.android.server.location;
Mike Lockwoode932f7f2009-04-06 10:51:26 -070018
Mike Lockwood628fd6d2010-01-25 22:46:13 -050019import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
Mike Lockwood03ca2162010-04-01 08:10:09 -070023import android.location.Criteria;
Mike Lockwoode932f7f2009-04-06 10:51:26 -070024import android.location.ILocationProvider;
25import android.location.Location;
Mike Lockwood03d24672009-10-08 15:45:03 -040026import android.net.NetworkInfo;
Mike Lockwoode932f7f2009-04-06 10:51:26 -070027import android.os.Bundle;
Mike Lockwood628fd6d2010-01-25 22:46:13 -050028import android.os.Handler;
Mike Lockwood3681f262009-05-12 10:52:03 -040029import android.os.IBinder;
Mike Lockwoode932f7f2009-04-06 10:51:26 -070030import android.os.RemoteException;
Mike Lockwood628fd6d2010-01-25 22:46:13 -050031import android.os.SystemClock;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070032import android.os.WorkSource;
Mike Lockwoode932f7f2009-04-06 10:51:26 -070033import android.util.Log;
34
Mike Lockwood00b74272010-03-26 10:41:48 -040035import com.android.internal.location.DummyLocationProvider;
36
Mike Lockwoode932f7f2009-04-06 10:51:26 -070037/**
Mike Lockwoodd03ff942010-02-09 08:46:14 -050038 * A class for proxying location providers implemented as services.
Mike Lockwoode932f7f2009-04-06 10:51:26 -070039 *
40 * {@hide}
41 */
Mike Lockwoodd03ff942010-02-09 08:46:14 -050042public class LocationProviderProxy implements LocationProviderInterface {
Mike Lockwoode932f7f2009-04-06 10:51:26 -070043
44 private static final String TAG = "LocationProviderProxy";
45
Mike Lockwood628fd6d2010-01-25 22:46:13 -050046 private final Context mContext;
Mike Lockwood15e3d0f2009-05-01 07:53:28 -040047 private final String mName;
Mark Vandevoorde8863c432010-10-04 14:23:24 -070048 private final Intent mIntent;
49 private final Handler mHandler;
50 private final Object mMutex = new Object(); // synchronizes access to non-final members
51 private Connection mServiceConnection = new Connection(); // never null
Mike Lockwood628fd6d2010-01-25 22:46:13 -050052
53 // cached values set by the location manager
Mike Lockwood15e3d0f2009-05-01 07:53:28 -040054 private boolean mLocationTracking = false;
Mike Lockwood2cd543a2010-02-01 12:16:35 -050055 private boolean mEnabled = false;
Mike Lockwood628fd6d2010-01-25 22:46:13 -050056 private long mMinTime = -1;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070057 private WorkSource mMinTimeSource = new WorkSource();
Mike Lockwood628fd6d2010-01-25 22:46:13 -050058 private int mNetworkState;
59 private NetworkInfo mNetworkInfo;
Mike Lockwoode932f7f2009-04-06 10:51:26 -070060
Mike Lockwood628fd6d2010-01-25 22:46:13 -050061 // constructor for proxying location providers implemented in a separate service
62 public LocationProviderProxy(Context context, String name, String serviceName,
63 Handler handler) {
64 mContext = context;
65 mName = name;
Mark Vandevoorde8863c432010-10-04 14:23:24 -070066 mIntent = new Intent(serviceName);
Mike Lockwood628fd6d2010-01-25 22:46:13 -050067 mHandler = handler;
Mark Vandevoorde8863c432010-10-04 14:23:24 -070068 mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
Mike Lockwood628fd6d2010-01-25 22:46:13 -050069 }
70
Mark Vandevoorde8863c432010-10-04 14:23:24 -070071 /**
72 * When unbundled NetworkLocationService package is updated, we
73 * need to unbind from the old version and re-bind to the new one.
74 */
Mike Lockwoode97ae402010-09-29 15:23:46 -040075 public void reconnect() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -070076 synchronized (mMutex) {
Mike Lockwoode97ae402010-09-29 15:23:46 -040077 mContext.unbindService(mServiceConnection);
Mark Vandevoorde8863c432010-10-04 14:23:24 -070078 mServiceConnection = new Connection();
79 mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
Mike Lockwoode97ae402010-09-29 15:23:46 -040080 }
81 }
82
Mark Vandevoorde8863c432010-10-04 14:23:24 -070083 private class Connection implements ServiceConnection, Runnable {
84
85 private ILocationProvider mProvider;
86
87 // for caching requiresNetwork, requiresSatellite, etc.
88 private DummyLocationProvider mCachedAttributes; // synchronized by mMutex
89
Mike Lockwood628fd6d2010-01-25 22:46:13 -050090 public void onServiceConnected(ComponentName className, IBinder service) {
91 Log.d(TAG, "LocationProviderProxy.onServiceConnected " + className);
Mark Vandevoorde8863c432010-10-04 14:23:24 -070092 synchronized (this) {
Mike Lockwood628fd6d2010-01-25 22:46:13 -050093 mProvider = ILocationProvider.Stub.asInterface(service);
94 if (mProvider != null) {
Mark Vandevoorde8863c432010-10-04 14:23:24 -070095 mHandler.post(this);
Mike Lockwood628fd6d2010-01-25 22:46:13 -050096 }
97 }
98 }
99
100 public void onServiceDisconnected(ComponentName className) {
101 Log.d(TAG, "LocationProviderProxy.onServiceDisconnected " + className);
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700102 synchronized (this) {
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500103 mProvider = null;
104 }
Mike Lockwood3681f262009-05-12 10:52:03 -0400105 }
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700106
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700107 public synchronized ILocationProvider getProvider() {
108 return mProvider;
109 }
110
111 public synchronized DummyLocationProvider getCachedAttributes() {
112 return mCachedAttributes;
113 }
114
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500115 public void run() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700116 synchronized (mMutex) {
117 if (mServiceConnection != this) {
118 // This ServiceConnection no longer the one we want to bind to.
119 return;
120 }
121 ILocationProvider provider = getProvider();
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500122 if (provider == null) {
123 return;
124 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500125
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700126 // resend previous values from the location manager if the service has restarted
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500127 try {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700128 if (mEnabled) {
129 provider.enable();
130 }
131 if (mLocationTracking) {
132 provider.enableLocationTracking(true);
133 }
134 if (mMinTime >= 0) {
135 provider.setMinTime(mMinTime, mMinTimeSource);
136 }
137 if (mNetworkInfo != null) {
138 provider.updateNetworkState(mNetworkState, mNetworkInfo);
139 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500140 } catch (RemoteException e) {
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500141 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500142
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700143 // init cache of parameters
144 if (mCachedAttributes == null) {
145 try {
146 mCachedAttributes = new DummyLocationProvider(mName, null);
147 mCachedAttributes.setRequiresNetwork(provider.requiresNetwork());
148 mCachedAttributes.setRequiresSatellite(provider.requiresSatellite());
149 mCachedAttributes.setRequiresCell(provider.requiresCell());
150 mCachedAttributes.setHasMonetaryCost(provider.hasMonetaryCost());
151 mCachedAttributes.setSupportsAltitude(provider.supportsAltitude());
152 mCachedAttributes.setSupportsSpeed(provider.supportsSpeed());
153 mCachedAttributes.setSupportsBearing(provider.supportsBearing());
154 mCachedAttributes.setPowerRequirement(provider.getPowerRequirement());
155 mCachedAttributes.setAccuracy(provider.getAccuracy());
156 } catch (RemoteException e) {
157 mCachedAttributes = null;
158 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500159 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500160 }
Suchi Amalapurapufff2fda2009-06-30 21:36:16 -0700161 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500162 };
Suchi Amalapurapufff2fda2009-06-30 21:36:16 -0700163
Mike Lockwood15e3d0f2009-05-01 07:53:28 -0400164 public String getName() {
165 return mName;
166 }
167
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700168 private DummyLocationProvider getCachedAttributes() {
169 synchronized (mMutex) {
170 return mServiceConnection.getCachedAttributes();
171 }
172 }
173
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700174 public boolean requiresNetwork() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700175 DummyLocationProvider cachedAttributes = getCachedAttributes();
176 if (cachedAttributes != null) {
177 return cachedAttributes.requiresNetwork();
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500178 } else {
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700179 return false;
180 }
181 }
182
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700183 public boolean requiresSatellite() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700184 DummyLocationProvider cachedAttributes = getCachedAttributes();
185 if (cachedAttributes != null) {
186 return cachedAttributes.requiresSatellite();
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500187 } else {
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700188 return false;
189 }
190 }
191
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700192 public boolean requiresCell() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700193 DummyLocationProvider cachedAttributes = getCachedAttributes();
194 if (cachedAttributes != null) {
195 return cachedAttributes.requiresCell();
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500196 } else {
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700197 return false;
198 }
199 }
200
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700201 public boolean hasMonetaryCost() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700202 DummyLocationProvider cachedAttributes = getCachedAttributes();
203 if (cachedAttributes != null) {
204 return cachedAttributes.hasMonetaryCost();
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500205 } else {
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700206 return false;
207 }
208 }
209
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700210 public boolean supportsAltitude() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700211 DummyLocationProvider cachedAttributes = getCachedAttributes();
212 if (cachedAttributes != null) {
213 return cachedAttributes.supportsAltitude();
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500214 } else {
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700215 return false;
216 }
217 }
218
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700219 public boolean supportsSpeed() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700220 DummyLocationProvider cachedAttributes = getCachedAttributes();
221 if (cachedAttributes != null) {
222 return cachedAttributes.supportsSpeed();
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500223 } else {
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700224 return false;
225 }
226 }
227
Mike Lockwood15e3d0f2009-05-01 07:53:28 -0400228 public boolean supportsBearing() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700229 DummyLocationProvider cachedAttributes = getCachedAttributes();
230 if (cachedAttributes != null) {
231 return cachedAttributes.supportsBearing();
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500232 } else {
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700233 return false;
234 }
235 }
236
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700237 public int getPowerRequirement() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700238 DummyLocationProvider cachedAttributes = getCachedAttributes();
239 if (cachedAttributes != null) {
240 return cachedAttributes.getPowerRequirement();
241 } else {
242 return -1;
243 }
244 }
245
246 public int getAccuracy() {
247 DummyLocationProvider cachedAttributes = getCachedAttributes();
248 if (cachedAttributes != null) {
249 return cachedAttributes.getAccuracy();
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500250 } else {
251 return -1;
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700252 }
253 }
254
Mike Lockwood03ca2162010-04-01 08:10:09 -0700255 public boolean meetsCriteria(Criteria criteria) {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700256 synchronized (mMutex) {
257 ILocationProvider provider = mServiceConnection.getProvider();
258 if (provider != null) {
259 try {
260 return provider.meetsCriteria(criteria);
261 } catch (RemoteException e) {
262 }
Mike Lockwood03ca2162010-04-01 08:10:09 -0700263 }
264 }
265 // default implementation if we lost connection to the provider
266 if ((criteria.getAccuracy() != Criteria.NO_REQUIREMENT) &&
267 (criteria.getAccuracy() < getAccuracy())) {
268 return false;
269 }
270 int criteriaPower = criteria.getPowerRequirement();
271 if ((criteriaPower != Criteria.NO_REQUIREMENT) &&
272 (criteriaPower < getPowerRequirement())) {
273 return false;
274 }
275 if (criteria.isAltitudeRequired() && !supportsAltitude()) {
276 return false;
277 }
278 if (criteria.isSpeedRequired() && !supportsSpeed()) {
279 return false;
280 }
281 if (criteria.isBearingRequired() && !supportsBearing()) {
282 return false;
283 }
284 return true;
285 }
286
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700287 public void enable() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700288 synchronized (mMutex) {
289 mEnabled = true;
290 ILocationProvider provider = mServiceConnection.getProvider();
291 if (provider != null) {
292 try {
293 provider.enable();
294 } catch (RemoteException e) {
295 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500296 }
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700297 }
298 }
299
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700300 public void disable() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700301 synchronized (mMutex) {
302 mEnabled = false;
303 ILocationProvider provider = mServiceConnection.getProvider();
304 if (provider != null) {
305 try {
306 provider.disable();
307 } catch (RemoteException e) {
308 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500309 }
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700310 }
311 }
312
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700313 public boolean isEnabled() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700314 synchronized (mMutex) {
315 return mEnabled;
316 }
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700317 }
318
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700319 public int getStatus(Bundle extras) {
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500320 ILocationProvider provider;
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700321 synchronized (mMutex) {
322 provider = mServiceConnection.getProvider();
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700323 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500324 if (provider != null) {
325 try {
326 return provider.getStatus(extras);
327 } catch (RemoteException e) {
328 }
329 }
330 return 0;
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700331 }
332
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700333 public long getStatusUpdateTime() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700334 ILocationProvider provider;
335 synchronized (mMutex) {
336 provider = mServiceConnection.getProvider();
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700337 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500338 if (provider != null) {
339 try {
340 return provider.getStatusUpdateTime();
341 } catch (RemoteException e) {
342 }
343 }
344 return 0;
345 }
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700346
Fred Fettinger3c8fbdf2010-01-04 15:38:13 -0600347 public String getInternalState() {
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500348 ILocationProvider provider;
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700349 synchronized (mMutex) {
350 provider = mServiceConnection.getProvider();
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500351 }
352 if (provider != null) {
353 try {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700354 return provider.getInternalState();
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500355 } catch (RemoteException e) {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700356 Log.e(TAG, "getInternalState failed", e);
357 }
358 }
359 return null;
360 }
361
362 public boolean isLocationTracking() {
363 synchronized (mMutex) {
364 return mLocationTracking;
365 }
366 }
367
368 public void enableLocationTracking(boolean enable) {
369 synchronized (mMutex) {
370 mLocationTracking = enable;
371 if (!enable) {
372 mMinTime = -1;
373 mMinTimeSource.clear();
374 }
375 ILocationProvider provider = mServiceConnection.getProvider();
376 if (provider != null) {
377 try {
378 provider.enableLocationTracking(enable);
379 } catch (RemoteException e) {
380 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500381 }
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700382 }
383 }
384
Mike Lockwood03ca2162010-04-01 08:10:09 -0700385 public boolean requestSingleShotFix() {
386 return false;
387 }
388
Mike Lockwood15e3d0f2009-05-01 07:53:28 -0400389 public long getMinTime() {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700390 synchronized (mMutex) {
391 return mMinTime;
392 }
Mike Lockwood15e3d0f2009-05-01 07:53:28 -0400393 }
394
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700395 public void setMinTime(long minTime, WorkSource ws) {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700396 synchronized (mMutex) {
397 mMinTime = minTime;
398 mMinTimeSource.set(ws);
399 ILocationProvider provider = mServiceConnection.getProvider();
400 if (provider != null) {
401 try {
402 provider.setMinTime(minTime, ws);
403 } catch (RemoteException e) {
404 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500405 }
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700406 }
407 }
408
Mike Lockwood03d24672009-10-08 15:45:03 -0400409 public void updateNetworkState(int state, NetworkInfo info) {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700410 synchronized (mMutex) {
411 mNetworkState = state;
412 mNetworkInfo = info;
413 ILocationProvider provider = mServiceConnection.getProvider();
414 if (provider != null) {
415 try {
416 provider.updateNetworkState(state, info);
417 } catch (RemoteException e) {
418 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500419 }
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700420 }
421 }
422
Mike Lockwoodfd6e5f02009-05-21 11:28:20 -0400423 public void updateLocation(Location location) {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700424 synchronized (mMutex) {
425 ILocationProvider provider = mServiceConnection.getProvider();
426 if (provider != null) {
427 try {
428 provider.updateLocation(location);
429 } catch (RemoteException e) {
430 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500431 }
Mike Lockwoodfd6e5f02009-05-21 11:28:20 -0400432 }
433 }
434
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700435 public boolean sendExtraCommand(String command, Bundle extras) {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700436 synchronized (mMutex) {
437 ILocationProvider provider = mServiceConnection.getProvider();
438 if (provider != null) {
439 try {
440 return provider.sendExtraCommand(command, extras);
441 } catch (RemoteException e) {
442 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500443 }
444 }
445 return false;
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700446 }
447
Mike Lockwood2f82c4e2009-04-17 08:24:10 -0400448 public void addListener(int uid) {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700449 synchronized (mMutex) {
450 ILocationProvider provider = mServiceConnection.getProvider();
451 if (provider != null) {
452 try {
453 provider.addListener(uid);
454 } catch (RemoteException e) {
455 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500456 }
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700457 }
458 }
459
Mike Lockwood2f82c4e2009-04-17 08:24:10 -0400460 public void removeListener(int uid) {
Mark Vandevoorde8863c432010-10-04 14:23:24 -0700461 synchronized (mMutex) {
462 ILocationProvider provider = mServiceConnection.getProvider();
463 if (provider != null) {
464 try {
465 provider.removeListener(uid);
466 } catch (RemoteException e) {
467 }
Mike Lockwood628fd6d2010-01-25 22:46:13 -0500468 }
469 }
Mike Lockwood3681f262009-05-12 10:52:03 -0400470 }
Mike Lockwoode932f7f2009-04-06 10:51:26 -0700471}