1/*
2 * Copyright (C) 2013 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
17package com.android.server.location;
18
19import android.hardware.location.GeofenceHardware;
20import android.hardware.location.GeofenceHardwareImpl;
21import android.hardware.location.GeofenceHardwareRequestParcelable;
22import android.hardware.location.IFusedLocationHardware;
23import android.hardware.location.IFusedLocationHardwareSink;
24import android.location.IFusedGeofenceHardware;
25import android.location.FusedBatchOptions;
26import android.location.Location;
27import android.location.LocationListener;
28import android.location.LocationManager;
29import android.location.LocationRequest;
30
31import android.content.Context;
32import android.os.Bundle;
33import android.os.Looper;
34import android.os.RemoteException;
35import android.os.SystemClock;
36import android.util.Log;
37
38/**
39 * This class is an interop layer for JVM types and the JNI code that interacts
40 * with the FLP HAL implementation.
41 *
42 * {@hide}
43 */
44public class FlpHardwareProvider {
45    private static final int FIRST_VERSION_WITH_FLUSH_LOCATIONS = 2;
46    private GeofenceHardwareImpl mGeofenceHardwareSink = null;
47    private IFusedLocationHardwareSink mLocationSink = null;
48    // Capabilities provided by FlpCallbacks
49    private boolean mHaveBatchingCapabilities;
50    private int mBatchingCapabilities;
51    private int mVersion = 1;
52
53    private static FlpHardwareProvider sSingletonInstance = null;
54
55    private final static String TAG = "FlpHardwareProvider";
56    private final Context mContext;
57    private final Object mLocationSinkLock = new Object();
58
59    // FlpHal result codes, they must be equal to the ones in fused_location.h
60    private static final int FLP_RESULT_SUCCESS = 0;
61    private static final int FLP_RESULT_ERROR = -1;
62    private static final int FLP_RESULT_INSUFFICIENT_MEMORY = -2;
63    private static final int FLP_RESULT_TOO_MANY_GEOFENCES = -3;
64    private static final int FLP_RESULT_ID_EXISTS = -4;
65    private static final int FLP_RESULT_ID_UNKNOWN = -5;
66    private static final int FLP_RESULT_INVALID_GEOFENCE_TRANSITION = -6;
67
68    // FlpHal monitor status codes, they must be equal to the ones in fused_location.h
69    private static final int FLP_GEOFENCE_MONITOR_STATUS_UNAVAILABLE = 1<<0;
70    private static final int FLP_GEOFENCE_MONITOR_STATUS_AVAILABLE = 1<<1;
71
72    public static FlpHardwareProvider getInstance(Context context) {
73        if (sSingletonInstance == null) {
74            sSingletonInstance = new FlpHardwareProvider(context);
75            sSingletonInstance.nativeInit();
76        }
77
78        return sSingletonInstance;
79    }
80
81    private FlpHardwareProvider(Context context) {
82        mContext = context;
83
84        // register for listening for passive provider data
85        LocationManager manager = (LocationManager) mContext.getSystemService(
86                Context.LOCATION_SERVICE);
87        final long minTime = 0;
88        final float minDistance = 0;
89        final boolean oneShot = false;
90        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
91                LocationManager.PASSIVE_PROVIDER,
92                minTime,
93                minDistance,
94                oneShot);
95        // Don't keep track of this request since it's done on behalf of other clients
96        // (which are kept track of separately).
97        request.setHideFromAppOps(true);
98        manager.requestLocationUpdates(
99                request,
100                new NetworkLocationListener(),
101                Looper.myLooper());
102    }
103
104    public static boolean isSupported() {
105        return nativeIsSupported();
106    }
107
108    /**
109     * Private callback functions used by FLP HAL.
110     */
111    // FlpCallbacks members
112    private void onLocationReport(Location[] locations) {
113        for (Location location : locations) {
114            location.setProvider(LocationManager.FUSED_PROVIDER);
115            // set the elapsed time-stamp just as GPS provider does
116            location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
117        }
118
119        IFusedLocationHardwareSink sink;
120        synchronized (mLocationSinkLock) {
121            sink = mLocationSink;
122        }
123        try {
124            if (sink != null) {
125                sink.onLocationAvailable(locations);
126            }
127        } catch (RemoteException e) {
128            Log.e(TAG, "RemoteException calling onLocationAvailable");
129        }
130    }
131
132    private void onBatchingCapabilities(int capabilities) {
133        synchronized (mLocationSinkLock) {
134            mHaveBatchingCapabilities = true;
135            mBatchingCapabilities = capabilities;
136        }
137
138        maybeSendCapabilities();
139
140        if (mGeofenceHardwareSink != null) {
141            mGeofenceHardwareSink.setVersion(getVersion());
142        }
143    }
144
145    private void onBatchingStatus(int status) {
146        IFusedLocationHardwareSink sink;
147        synchronized (mLocationSinkLock) {
148            sink = mLocationSink;
149        }
150        try {
151            if (sink != null) {
152                sink.onStatusChanged(status);
153            }
154        } catch (RemoteException e) {
155            Log.e(TAG, "RemoteException calling onBatchingStatus");
156        }
157    }
158
159    // Returns the current version of the FLP HAL.  This depends both on the version of the
160    // structure returned by the hardware layer, and whether or not we've received the
161    // capabilities callback on initialization.  Assume original version until we get
162    // the new initialization callback.
163    private int getVersion() {
164        synchronized (mLocationSinkLock) {
165            if (mHaveBatchingCapabilities) {
166                return mVersion;
167            }
168        }
169        return 1;
170    }
171
172    private void setVersion(int version) {
173        mVersion = version;
174        if (mGeofenceHardwareSink != null) {
175            mGeofenceHardwareSink.setVersion(getVersion());
176        }
177    }
178
179    private void maybeSendCapabilities() {
180        IFusedLocationHardwareSink sink;
181        boolean haveBatchingCapabilities;
182        int batchingCapabilities;
183        synchronized (mLocationSinkLock) {
184            sink = mLocationSink;
185            haveBatchingCapabilities = mHaveBatchingCapabilities;
186            batchingCapabilities = mBatchingCapabilities;
187        }
188        try {
189            if (sink != null && haveBatchingCapabilities) {
190                sink.onCapabilities(batchingCapabilities);
191            }
192        } catch (RemoteException e) {
193            Log.e(TAG, "RemoteException calling onLocationAvailable");
194        }
195    }
196
197    // FlpDiagnosticCallbacks members
198    private void onDataReport(String data) {
199        IFusedLocationHardwareSink sink;
200        synchronized (mLocationSinkLock) {
201            sink = mLocationSink;
202        }
203        try {
204            if (mLocationSink != null) {
205                sink.onDiagnosticDataAvailable(data);
206            }
207        } catch (RemoteException e) {
208            Log.e(TAG, "RemoteException calling onDiagnosticDataAvailable");
209        }
210    }
211
212    // FlpGeofenceCallbacks members
213    private void onGeofenceTransition(
214            int geofenceId,
215            Location location,
216            int transition,
217            long timestamp,
218            int sourcesUsed) {
219        // the transition Id does not require translation because the values in fused_location.h
220        // and GeofenceHardware are in sync
221        getGeofenceHardwareSink().reportGeofenceTransition(
222                geofenceId,
223                updateLocationInformation(location),
224                transition,
225                timestamp,
226                GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE,
227                sourcesUsed);
228    }
229
230    private void onGeofenceMonitorStatus(int status, int source, Location location) {
231        // allow the location to be optional in this event
232        Location updatedLocation = null;
233        if(location != null) {
234            updatedLocation = updateLocationInformation(location);
235        }
236
237        int monitorStatus;
238        switch (status) {
239            case FLP_GEOFENCE_MONITOR_STATUS_UNAVAILABLE:
240                monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE;
241                break;
242            case FLP_GEOFENCE_MONITOR_STATUS_AVAILABLE:
243                monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE;
244                break;
245            default:
246                Log.e(TAG, "Invalid FlpHal Geofence monitor status: " + status);
247                monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE;
248                break;
249        }
250
251        getGeofenceHardwareSink().reportGeofenceMonitorStatus(
252                GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE,
253                monitorStatus,
254                updatedLocation,
255                source);
256    }
257
258    private void onGeofenceAdd(int geofenceId, int result) {
259        getGeofenceHardwareSink().reportGeofenceAddStatus(
260                geofenceId,
261                translateToGeofenceHardwareStatus(result));
262    }
263
264    private void onGeofenceRemove(int geofenceId, int result) {
265        getGeofenceHardwareSink().reportGeofenceRemoveStatus(
266                geofenceId,
267                translateToGeofenceHardwareStatus(result));
268    }
269
270    private void onGeofencePause(int geofenceId, int result) {
271        getGeofenceHardwareSink().reportGeofencePauseStatus(
272                geofenceId,
273                translateToGeofenceHardwareStatus(result));
274    }
275
276    private void onGeofenceResume(int geofenceId, int result) {
277        getGeofenceHardwareSink().reportGeofenceResumeStatus(
278                geofenceId,
279                translateToGeofenceHardwareStatus(result));
280    }
281
282    private void onGeofencingCapabilities(int capabilities) {
283        getGeofenceHardwareSink().onCapabilities(capabilities);
284    }
285
286    /**
287     * Private native methods accessing FLP HAL.
288     */
289    static { nativeClassInit(); }
290
291    // Core members
292    private static native void nativeClassInit();
293    private static native boolean nativeIsSupported();
294
295    // FlpLocationInterface members
296    private native void nativeInit();
297    private native int nativeGetBatchSize();
298    private native void nativeStartBatching(int requestId, FusedBatchOptions options);
299    private native void nativeUpdateBatchingOptions(int requestId, FusedBatchOptions optionsObject);
300    private native void nativeStopBatching(int id);
301    private native void nativeRequestBatchedLocation(int lastNLocations);
302    private native void nativeFlushBatchedLocations();
303    private native void nativeInjectLocation(Location location);
304    // TODO [Fix] sort out the lifetime of the instance
305    private native void nativeCleanup();
306
307    // FlpDiagnosticsInterface members
308    private native boolean nativeIsDiagnosticSupported();
309    private native void nativeInjectDiagnosticData(String data);
310
311    // FlpDeviceContextInterface members
312    private native boolean nativeIsDeviceContextSupported();
313    private native void nativeInjectDeviceContext(int deviceEnabledContext);
314
315    // FlpGeofencingInterface members
316    private native boolean nativeIsGeofencingSupported();
317    private native void nativeAddGeofences(
318            GeofenceHardwareRequestParcelable[] geofenceRequestsArray);
319    private native void nativePauseGeofence(int geofenceId);
320    private native void  nativeResumeGeofence(int geofenceId, int monitorTransitions);
321    private native void nativeModifyGeofenceOption(
322        int geofenceId,
323        int lastTransition,
324        int monitorTransitions,
325        int notificationResponsiveness,
326        int unknownTimer,
327        int sourcesToUse);
328    private native void nativeRemoveGeofences(int[] geofenceIdsArray);
329
330    /**
331     * Interface implementations for services built on top of this functionality.
332     */
333    public static final String LOCATION = "Location";
334    public static final String GEOFENCING = "Geofencing";
335
336    public IFusedLocationHardware getLocationHardware() {
337        return mLocationHardware;
338    }
339
340    public IFusedGeofenceHardware getGeofenceHardware() {
341        return mGeofenceHardwareService;
342    }
343
344    private final IFusedLocationHardware mLocationHardware = new IFusedLocationHardware.Stub() {
345        @Override
346        public void registerSink(IFusedLocationHardwareSink eventSink) {
347            synchronized (mLocationSinkLock) {
348                // only one sink is allowed at the moment
349                if (mLocationSink != null) {
350                    Log.e(TAG, "Replacing an existing IFusedLocationHardware sink");
351                }
352
353                mLocationSink = eventSink;
354            }
355            maybeSendCapabilities();
356        }
357
358        @Override
359        public void unregisterSink(IFusedLocationHardwareSink eventSink) {
360            synchronized (mLocationSinkLock) {
361                // don't throw if the sink is not registered, simply make it a no-op
362                if (mLocationSink == eventSink) {
363                    mLocationSink = null;
364                }
365            }
366        }
367
368        @Override
369        public int getSupportedBatchSize() {
370            return nativeGetBatchSize();
371        }
372
373        @Override
374        public void startBatching(int requestId, FusedBatchOptions options) {
375            nativeStartBatching(requestId, options);
376        }
377
378        @Override
379        public void stopBatching(int requestId) {
380            nativeStopBatching(requestId);
381        }
382
383        @Override
384        public void updateBatchingOptions(int requestId, FusedBatchOptions options) {
385            nativeUpdateBatchingOptions(requestId, options);
386        }
387
388        @Override
389        public void requestBatchOfLocations(int batchSizeRequested) {
390            nativeRequestBatchedLocation(batchSizeRequested);
391        }
392
393        @Override
394        public void flushBatchedLocations() {
395            if (getVersion() >= FIRST_VERSION_WITH_FLUSH_LOCATIONS) {
396                nativeFlushBatchedLocations();
397            } else {
398                Log.wtf(TAG,
399                        "Tried to call flushBatchedLocations on an unsupported implementation");
400            }
401        }
402
403        @Override
404        public boolean supportsDiagnosticDataInjection() {
405            return nativeIsDiagnosticSupported();
406        }
407
408        @Override
409        public void injectDiagnosticData(String data) {
410            nativeInjectDiagnosticData(data);
411        }
412
413        @Override
414        public boolean supportsDeviceContextInjection() {
415            return nativeIsDeviceContextSupported();
416        }
417
418        @Override
419        public void injectDeviceContext(int deviceEnabledContext) {
420            nativeInjectDeviceContext(deviceEnabledContext);
421        }
422
423        @Override
424        public int getVersion() {
425            return FlpHardwareProvider.this.getVersion();
426        }
427    };
428
429    private final IFusedGeofenceHardware mGeofenceHardwareService =
430            new IFusedGeofenceHardware.Stub() {
431        @Override
432        public boolean isSupported() {
433            return nativeIsGeofencingSupported();
434        }
435
436        @Override
437        public void addGeofences(GeofenceHardwareRequestParcelable[] geofenceRequestsArray) {
438            nativeAddGeofences(geofenceRequestsArray);
439        }
440
441        @Override
442        public void removeGeofences(int[] geofenceIds) {
443            nativeRemoveGeofences(geofenceIds);
444        }
445
446        @Override
447        public void pauseMonitoringGeofence(int geofenceId) {
448            nativePauseGeofence(geofenceId);
449        }
450
451        @Override
452        public void resumeMonitoringGeofence(int geofenceId, int monitorTransitions) {
453            nativeResumeGeofence(geofenceId, monitorTransitions);
454        }
455
456        @Override
457        public void modifyGeofenceOptions(int geofenceId,
458                int lastTransition,
459                int monitorTransitions,
460                int notificationResponsiveness,
461                int unknownTimer,
462                int sourcesToUse) {
463            nativeModifyGeofenceOption(
464                    geofenceId,
465                    lastTransition,
466                    monitorTransitions,
467                    notificationResponsiveness,
468                    unknownTimer,
469                    sourcesToUse);
470        }
471    };
472
473    /**
474     * Internal classes and functions used by the provider.
475     */
476    private final class NetworkLocationListener implements LocationListener {
477        @Override
478        public void onLocationChanged(Location location) {
479            if (
480                !LocationManager.NETWORK_PROVIDER.equals(location.getProvider()) ||
481                !location.hasAccuracy()
482                ) {
483                return;
484            }
485
486            nativeInjectLocation(location);
487        }
488
489        @Override
490        public void onStatusChanged(String provider, int status, Bundle extras) { }
491
492        @Override
493        public void onProviderEnabled(String provider) { }
494
495        @Override
496        public void onProviderDisabled(String provider) { }
497    }
498
499    private GeofenceHardwareImpl getGeofenceHardwareSink() {
500        if (mGeofenceHardwareSink == null) {
501            mGeofenceHardwareSink = GeofenceHardwareImpl.getInstance(mContext);
502            mGeofenceHardwareSink.setVersion(getVersion());
503        }
504
505        return mGeofenceHardwareSink;
506    }
507
508    private static int translateToGeofenceHardwareStatus(int flpHalResult) {
509        switch(flpHalResult) {
510            case FLP_RESULT_SUCCESS:
511                return GeofenceHardware.GEOFENCE_SUCCESS;
512            case FLP_RESULT_ERROR:
513                return GeofenceHardware.GEOFENCE_FAILURE;
514            case FLP_RESULT_INSUFFICIENT_MEMORY:
515                return GeofenceHardware.GEOFENCE_ERROR_INSUFFICIENT_MEMORY;
516            case FLP_RESULT_TOO_MANY_GEOFENCES:
517                return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES;
518            case FLP_RESULT_ID_EXISTS:
519                return GeofenceHardware.GEOFENCE_ERROR_ID_EXISTS;
520            case FLP_RESULT_ID_UNKNOWN:
521                return GeofenceHardware.GEOFENCE_ERROR_ID_UNKNOWN;
522            case FLP_RESULT_INVALID_GEOFENCE_TRANSITION:
523                return GeofenceHardware.GEOFENCE_ERROR_INVALID_TRANSITION;
524            default:
525                Log.e(TAG, String.format("Invalid FlpHal result code: %d", flpHalResult));
526                return GeofenceHardware.GEOFENCE_FAILURE;
527        }
528    }
529
530    private Location updateLocationInformation(Location location) {
531        location.setProvider(LocationManager.FUSED_PROVIDER);
532        // set the elapsed time-stamp just as GPS provider does
533        location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
534        return location;
535    }
536}
537