1/*
2 * Copyright (C) 2017 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 android.net.lowpan;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.os.Handler;
22import android.os.RemoteException;
23import android.os.ServiceSpecificException;
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.HashMap;
27import java.util.Map;
28
29/**
30 * LoWPAN Scanner
31 *
32 * <p>This class allows performing network (active) scans and energy (passive) scans.
33 *
34 * @see LowpanInterface
35 * @hide
36 */
37// @SystemApi
38public class LowpanScanner {
39    private static final String TAG = LowpanScanner.class.getSimpleName();
40
41    // Public Classes
42
43    /**
44     * Callback base class for LowpanScanner
45     *
46     * @hide
47     */
48    // @SystemApi
49    public abstract static class Callback {
50        public void onNetScanBeacon(LowpanBeaconInfo beacon) {}
51
52        public void onEnergyScanResult(LowpanEnergyScanResult result) {}
53
54        public void onScanFinished() {}
55    }
56
57    // Instance Variables
58
59    private ILowpanInterface mBinder;
60    private Callback mCallback = null;
61    private Handler mHandler = null;
62    private ArrayList<Integer> mChannelMask = null;
63    private int mTxPower = Integer.MAX_VALUE;
64
65    // Constructors/Accessors and Exception Glue
66
67    LowpanScanner(@NonNull ILowpanInterface binder) {
68        mBinder = binder;
69    }
70
71    /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
72    public synchronized void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
73        mCallback = cb;
74        mHandler = handler;
75    }
76
77    /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
78    public void setCallback(@Nullable Callback cb) {
79        setCallback(cb, null);
80    }
81
82    /**
83     * Sets the channel mask to use when scanning.
84     *
85     * @param mask The channel mask to use when scanning. If <code>null</code>, any previously set
86     *     channel mask will be cleared and all channels not masked by the current regulatory zone
87     *     will be scanned.
88     */
89    public void setChannelMask(@Nullable Collection<Integer> mask) {
90        if (mask == null) {
91            mChannelMask = null;
92        } else {
93            if (mChannelMask == null) {
94                mChannelMask = new ArrayList<>();
95            } else {
96                mChannelMask.clear();
97            }
98            mChannelMask.addAll(mask);
99        }
100    }
101
102    /**
103     * Gets the current channel mask.
104     *
105     * @return the current channel mask, or <code>null</code> if no channel mask is currently set.
106     */
107    public @Nullable Collection<Integer> getChannelMask() {
108        return (Collection<Integer>) mChannelMask.clone();
109    }
110
111    /**
112     * Adds a channel to the channel mask used for scanning.
113     *
114     * <p>If a channel mask was previously <code>null</code>, a new one is created containing only
115     * this channel. May be called multiple times to add additional channels ot the channel mask.
116     *
117     * @see #setChannelMask
118     * @see #getChannelMask
119     * @see #getTxPower
120     */
121    public void addChannel(int channel) {
122        if (mChannelMask == null) {
123            mChannelMask = new ArrayList<>();
124        }
125        mChannelMask.add(Integer.valueOf(channel));
126    }
127
128    /**
129     * Sets the maximum transmit power to be used for active scanning.
130     *
131     * <p>The actual transmit power used is the lesser of this value and the currently configured
132     * maximum transmit power for the interface.
133     *
134     * @see #getTxPower
135     */
136    public void setTxPower(int txPower) {
137        mTxPower = txPower;
138    }
139
140    /**
141     * Gets the maximum transmit power used for active scanning.
142     *
143     * @see #setTxPower
144     */
145    public int getTxPower() {
146        return mTxPower;
147    }
148
149    private Map<String, Object> createScanOptionMap() {
150        Map<String, Object> map = new HashMap();
151
152        if (mChannelMask != null) {
153            LowpanProperties.KEY_CHANNEL_MASK.putInMap(
154                    map, mChannelMask.stream().mapToInt(i -> i).toArray());
155        }
156
157        if (mTxPower != Integer.MAX_VALUE) {
158            LowpanProperties.KEY_MAX_TX_POWER.putInMap(map, Integer.valueOf(mTxPower));
159        }
160
161        return map;
162    }
163
164    /**
165     * Start a network scan.
166     *
167     * <p>This method will return once the scan has started.
168     *
169     * @see #stopNetScan
170     */
171    public void startNetScan() throws LowpanException {
172        Map<String, Object> map = createScanOptionMap();
173
174        ILowpanNetScanCallback binderListener =
175                new ILowpanNetScanCallback.Stub() {
176                    public void onNetScanBeacon(LowpanBeaconInfo beaconInfo) {
177                        Callback callback;
178                        Handler handler;
179
180                        synchronized (LowpanScanner.this) {
181                            callback = mCallback;
182                            handler = mHandler;
183                        }
184
185                        if (callback == null) {
186                            return;
187                        }
188
189                        Runnable runnable = () -> callback.onNetScanBeacon(beaconInfo);
190
191                        if (handler != null) {
192                            handler.post(runnable);
193                        } else {
194                            runnable.run();
195                        }
196                    }
197
198                    public void onNetScanFinished() {
199                        Callback callback;
200                        Handler handler;
201
202                        synchronized (LowpanScanner.this) {
203                            callback = mCallback;
204                            handler = mHandler;
205                        }
206
207                        if (callback == null) {
208                            return;
209                        }
210
211                        Runnable runnable = () -> callback.onScanFinished();
212
213                        if (handler != null) {
214                            handler.post(runnable);
215                        } else {
216                            runnable.run();
217                        }
218                    }
219                };
220
221        try {
222            mBinder.startNetScan(map, binderListener);
223
224        } catch (RemoteException x) {
225            throw x.rethrowAsRuntimeException();
226
227        } catch (ServiceSpecificException x) {
228            throw LowpanException.rethrowFromServiceSpecificException(x);
229        }
230    }
231
232    /**
233     * Stop a network scan currently in progress.
234     *
235     * @see #startNetScan
236     */
237    public void stopNetScan() {
238        try {
239            mBinder.stopNetScan();
240
241        } catch (RemoteException x) {
242            throw x.rethrowAsRuntimeException();
243        }
244    }
245
246    /**
247     * Start an energy scan.
248     *
249     * <p>This method will return once the scan has started.
250     *
251     * @see #stopEnergyScan
252     */
253    public void startEnergyScan() throws LowpanException {
254        Map<String, Object> map = createScanOptionMap();
255
256        ILowpanEnergyScanCallback binderListener =
257                new ILowpanEnergyScanCallback.Stub() {
258                    public void onEnergyScanResult(int channel, int rssi) {
259                        Callback callback = mCallback;
260                        Handler handler = mHandler;
261
262                        if (callback == null) {
263                            return;
264                        }
265
266                        Runnable runnable =
267                                () -> {
268                                    if (callback != null) {
269                                        LowpanEnergyScanResult result =
270                                                new LowpanEnergyScanResult();
271                                        result.setChannel(channel);
272                                        result.setMaxRssi(rssi);
273                                        callback.onEnergyScanResult(result);
274                                    }
275                                };
276
277                        if (handler != null) {
278                            handler.post(runnable);
279                        } else {
280                            runnable.run();
281                        }
282                    }
283
284                    public void onEnergyScanFinished() {
285                        Callback callback = mCallback;
286                        Handler handler = mHandler;
287
288                        if (callback == null) {
289                            return;
290                        }
291
292                        Runnable runnable = () -> callback.onScanFinished();
293
294                        if (handler != null) {
295                            handler.post(runnable);
296                        } else {
297                            runnable.run();
298                        }
299                    }
300                };
301
302        try {
303            mBinder.startEnergyScan(map, binderListener);
304
305        } catch (RemoteException x) {
306            throw x.rethrowAsRuntimeException();
307
308        } catch (ServiceSpecificException x) {
309            throw LowpanException.rethrowFromServiceSpecificException(x);
310        }
311    }
312
313    /**
314     * Stop an energy scan currently in progress.
315     *
316     * @see #startEnergyScan
317     */
318    public void stopEnergyScan() {
319        try {
320            mBinder.stopEnergyScan();
321
322        } catch (RemoteException x) {
323            throw x.rethrowAsRuntimeException();
324        }
325    }
326}
327