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.location.provider;
18
19import android.hardware.location.IFusedLocationHardware;
20import android.hardware.location.IFusedLocationHardwareSink;
21
22import android.location.Location;
23
24import android.os.Handler;
25import android.os.Looper;
26import android.os.Message;
27import android.os.RemoteException;
28import android.util.Log;
29
30import java.util.HashMap;
31import java.util.Map;
32
33/**
34 * Class that exposes IFusedLocationHardware functionality to unbundled services.
35 */
36public final class FusedLocationHardware {
37    private final String TAG = "FusedLocationHardware";
38
39    private IFusedLocationHardware mLocationHardware;
40
41    // the list uses a copy-on-write pattern to update its contents
42    HashMap<FusedLocationHardwareSink, DispatcherHandler> mSinkList =
43            new HashMap<FusedLocationHardwareSink, DispatcherHandler>();
44
45    private IFusedLocationHardwareSink mInternalSink = new IFusedLocationHardwareSink.Stub() {
46        @Override
47        public void onLocationAvailable(Location[] locations) {
48            dispatchLocations(locations);
49        }
50
51        @Override
52        public void onDiagnosticDataAvailable(String data) {
53            dispatchDiagnosticData(data);
54        }
55    };
56
57    /**
58     * @hide
59     */
60    public FusedLocationHardware(IFusedLocationHardware locationHardware) {
61        mLocationHardware = locationHardware;
62    }
63
64    /*
65     * Methods to provide a Facade for IFusedLocationHardware
66     */
67    public void registerSink(FusedLocationHardwareSink sink, Looper looper) {
68        if(sink == null || looper == null) {
69            throw new IllegalArgumentException("Parameter sink and looper cannot be null.");
70        }
71
72        boolean registerSink;
73        synchronized (mSinkList) {
74            // register only on first insertion
75            registerSink = mSinkList.size() == 0;
76            // guarantee uniqueness
77            if(mSinkList.containsKey(sink)) {
78                return;
79            }
80
81            HashMap<FusedLocationHardwareSink, DispatcherHandler> newSinkList =
82                    new HashMap<FusedLocationHardwareSink, DispatcherHandler>(mSinkList);
83            newSinkList.put(sink, new DispatcherHandler(looper));
84            mSinkList = newSinkList;
85        }
86
87        if(registerSink) {
88            try {
89                mLocationHardware.registerSink(mInternalSink);
90            } catch(RemoteException e) {
91                Log.e(TAG, "RemoteException at registerSink");
92            }
93        }
94    }
95
96    public void unregisterSink(FusedLocationHardwareSink sink) {
97        if(sink == null) {
98            throw new IllegalArgumentException("Parameter sink cannot be null.");
99        }
100
101        boolean unregisterSink;
102        synchronized(mSinkList) {
103            if(!mSinkList.containsKey(sink)) {
104                //done
105                return;
106            }
107
108            HashMap<FusedLocationHardwareSink, DispatcherHandler> newSinkList =
109                    new HashMap<FusedLocationHardwareSink, DispatcherHandler>(mSinkList);
110            newSinkList.remove(sink);
111            //unregister after the last sink
112            unregisterSink = newSinkList.size() == 0;
113
114            mSinkList = newSinkList;
115        }
116
117        if(unregisterSink) {
118            try {
119                mLocationHardware.unregisterSink(mInternalSink);
120            } catch(RemoteException e) {
121                Log.e(TAG, "RemoteException at unregisterSink");
122            }
123        }
124    }
125
126    public int getSupportedBatchSize() {
127        try {
128            return mLocationHardware.getSupportedBatchSize();
129        } catch(RemoteException e) {
130            Log.e(TAG, "RemoteException at getSupportedBatchSize");
131            return 0;
132        }
133    }
134
135    public void startBatching(int id, GmsFusedBatchOptions batchOptions) {
136        try {
137            mLocationHardware.startBatching(id, batchOptions.getParcelableOptions());
138        } catch(RemoteException e) {
139            Log.e(TAG, "RemoteException at startBatching");
140        }
141    }
142
143    public void stopBatching(int id) {
144        try {
145            mLocationHardware.stopBatching(id);
146        } catch(RemoteException e) {
147            Log.e(TAG, "RemoteException at stopBatching");
148        }
149    }
150
151    public void updateBatchingOptions(int id, GmsFusedBatchOptions batchOptions) {
152        try {
153            mLocationHardware.updateBatchingOptions(id, batchOptions.getParcelableOptions());
154        } catch(RemoteException e) {
155            Log.e(TAG, "RemoteException at updateBatchingOptions");
156        }
157    }
158
159    public void requestBatchOfLocations(int batchSizeRequest) {
160        try {
161            mLocationHardware.requestBatchOfLocations(batchSizeRequest);
162        } catch(RemoteException e) {
163            Log.e(TAG, "RemoteException at requestBatchOfLocations");
164        }
165    }
166
167    public boolean supportsDiagnosticDataInjection() {
168        try {
169            return mLocationHardware.supportsDiagnosticDataInjection();
170        } catch(RemoteException e) {
171            Log.e(TAG, "RemoteException at supportsDiagnisticDataInjection");
172            return false;
173        }
174    }
175
176    public void injectDiagnosticData(String data) {
177        try {
178            mLocationHardware.injectDiagnosticData(data);
179        } catch(RemoteException e) {
180            Log.e(TAG, "RemoteException at injectDiagnosticData");
181        }
182    }
183
184    public boolean supportsDeviceContextInjection() {
185        try {
186            return mLocationHardware.supportsDeviceContextInjection();
187        } catch(RemoteException e) {
188            Log.e(TAG, "RemoteException at supportsDeviceContextInjection");
189            return false;
190        }
191    }
192
193    public void injectDeviceContext(int deviceEnabledContext) {
194        try {
195            mLocationHardware.injectDeviceContext(deviceEnabledContext);
196        } catch(RemoteException e) {
197            Log.e(TAG, "RemoteException at injectDeviceContext");
198        }
199    }
200
201    /*
202     * Helper methods and classes
203     */
204    private class DispatcherHandler extends Handler {
205        public static final int DISPATCH_LOCATION = 1;
206        public static final int DISPATCH_DIAGNOSTIC_DATA = 2;
207
208        public DispatcherHandler(Looper looper) {
209            super(looper, null /*callback*/ , true /*async*/);
210        }
211
212        @Override
213        public void handleMessage(Message message) {
214            MessageCommand command = (MessageCommand) message.obj;
215            switch(message.what) {
216                case DISPATCH_LOCATION:
217                    command.dispatchLocation();
218                    break;
219                case DISPATCH_DIAGNOSTIC_DATA:
220                    command.dispatchDiagnosticData();
221                default:
222                    Log.e(TAG, "Invalid dispatch message");
223                    break;
224            }
225        }
226    }
227
228    private class MessageCommand {
229        private final FusedLocationHardwareSink mSink;
230        private final Location[] mLocations;
231        private final String mData;
232
233        public MessageCommand(
234                FusedLocationHardwareSink sink,
235                Location[] locations,
236                String data) {
237            mSink = sink;
238            mLocations = locations;
239            mData = data;
240        }
241
242        public void dispatchLocation() {
243            mSink.onLocationAvailable(mLocations);
244        }
245
246        public void dispatchDiagnosticData() {
247            mSink.onDiagnosticDataAvailable(mData);
248        }
249    }
250
251    private void dispatchLocations(Location[] locations) {
252        HashMap<FusedLocationHardwareSink, DispatcherHandler> sinks;
253        synchronized (mSinkList) {
254            sinks = mSinkList;
255        }
256
257        for(Map.Entry<FusedLocationHardwareSink, DispatcherHandler> entry : sinks.entrySet()) {
258            Message message = Message.obtain(
259                    entry.getValue(),
260                    DispatcherHandler.DISPATCH_LOCATION,
261                    new MessageCommand(entry.getKey(), locations, null /*data*/));
262            message.sendToTarget();
263        }
264    }
265
266    private void dispatchDiagnosticData(String data) {
267        HashMap<FusedLocationHardwareSink, DispatcherHandler> sinks;
268        synchronized(mSinkList) {
269            sinks = mSinkList;
270        }
271
272        for(Map.Entry<FusedLocationHardwareSink, DispatcherHandler> entry : sinks.entrySet()) {
273            Message message = Message.obtain(
274                    entry.getValue(),
275                    DispatcherHandler.DISPATCH_DIAGNOSTIC_DATA,
276                    new MessageCommand(entry.getKey(), null /*locations*/, data));
277            message.sendToTarget();
278        }
279    }
280}
281