content: /*
   * Copyright (C) 2017 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 android.net.lowpan;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.os.Handler;
  import android.os.RemoteException;
  import android.os.ServiceSpecificException;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.Map;
  
  /**
   * LoWPAN Scanner
   *
   * <p>This class allows performing network (active) scans and energy (passive) scans.
   *
   * @see LowpanInterface
   * @hide
   */
  // @SystemApi
  public class LowpanScanner {
      private static final String TAG = LowpanScanner.class.getSimpleName();
  
      // Public Classes
  
      /**
       * Callback base class for LowpanScanner
       *
       * @hide
       */
      // @SystemApi
      public abstract static class Callback {
          public void onNetScanBeacon(LowpanBeaconInfo beacon) {}
  
          public void onEnergyScanResult(LowpanEnergyScanResult result) {}
  
          public void onScanFinished() {}
      }
  
      // Instance Variables
  
      private ILowpanInterface mBinder;
      private Callback mCallback = null;
      private Handler mHandler = null;
      private ArrayList<Integer> mChannelMask = null;
      private int mTxPower = Integer.MAX_VALUE;
  
      // Constructors/Accessors and Exception Glue
  
      LowpanScanner(@NonNull ILowpanInterface binder) {
          mBinder = binder;
      }
  
      /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
      public synchronized void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
          mCallback = cb;
          mHandler = handler;
      }
  
      /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
      public void setCallback(@Nullable Callback cb) {
          setCallback(cb, null);
      }
  
      /**
       * Sets the channel mask to use when scanning.
       *
       * @param mask The channel mask to use when scanning. If <code>null</code>, any previously set
       *     channel mask will be cleared and all channels not masked by the current regulatory zone
       *     will be scanned.
       */
      public void setChannelMask(@Nullable Collection<Integer> mask) {
          if (mask == null) {
              mChannelMask = null;
          } else {
              if (mChannelMask == null) {
                  mChannelMask = new ArrayList<>();
              } else {
                  mChannelMask.clear();
              }
              mChannelMask.addAll(mask);
          }
      }
  
      /**
       * Gets the current channel mask.
       *
       * @return the current channel mask, or <code>null</code> if no channel mask is currently set.
       */
      public @Nullable Collection<Integer> getChannelMask() {
          return (Collection<Integer>) mChannelMask.clone();
      }
  
      /**
       * Adds a channel to the channel mask used for scanning.
       *
       * <p>If a channel mask was previously <code>null</code>, a new one is created containing only
       * this channel. May be called multiple times to add additional channels ot the channel mask.
       *
       * @see #setChannelMask
       * @see #getChannelMask
       * @see #getTxPower
       */
      public void addChannel(int channel) {
          if (mChannelMask == null) {
              mChannelMask = new ArrayList<>();
          }
          mChannelMask.add(Integer.valueOf(channel));
      }
  
      /**
       * Sets the maximum transmit power to be used for active scanning.
       *
       * <p>The actual transmit power used is the lesser of this value and the currently configured
       * maximum transmit power for the interface.
       *
       * @see #getTxPower
       */
      public void setTxPower(int txPower) {
          mTxPower = txPower;
      }
  
      /**
       * Gets the maximum transmit power used for active scanning.
       *
       * @see #setTxPower
       */
      public int getTxPower() {
          return mTxPower;
      }
  
      private Map<String, Object> createScanOptionMap() {
          Map<String, Object> map = new HashMap();
  
          if (mChannelMask != null) {
              LowpanProperties.KEY_CHANNEL_MASK.putInMap(
                      map, mChannelMask.stream().mapToInt(i -> i).toArray());
          }
  
          if (mTxPower != Integer.MAX_VALUE) {
              LowpanProperties.KEY_MAX_TX_POWER.putInMap(map, Integer.valueOf(mTxPower));
          }
  
          return map;
      }
  
      /**
       * Start a network scan.
       *
       * <p>This method will return once the scan has started.
       *
       * @see #stopNetScan
       */
      public void startNetScan() throws LowpanException {
          Map<String, Object> map = createScanOptionMap();
  
          ILowpanNetScanCallback binderListener =
                  new ILowpanNetScanCallback.Stub() {
                      public void onNetScanBeacon(LowpanBeaconInfo beaconInfo) {
                          Callback callback;
                          Handler handler;
  
                          synchronized (LowpanScanner.this) {
                              callback = mCallback;
                              handler = mHandler;
                          }
  
                          if (callback == null) {
                              return;
                          }
  
                          Runnable runnable = () -> callback.onNetScanBeacon(beaconInfo);
  
                          if (handler != null) {
                              handler.post(runnable);
                          } else {
                              runnable.run();
                          }
                      }
  
                      public void onNetScanFinished() {
                          Callback callback;
                          Handler handler;
  
                          synchronized (LowpanScanner.this) {
                              callback = mCallback;
                              handler = mHandler;
                          }
  
                          if (callback == null) {
                              return;
                          }
  
                          Runnable runnable = () -> callback.onScanFinished();
  
                          if (handler != null) {
                              handler.post(runnable);
                          } else {
                              runnable.run();
                          }
                      }
                  };
  
          try {
              mBinder.startNetScan(map, binderListener);
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      /**
       * Stop a network scan currently in progress.
       *
       * @see #startNetScan
       */
      public void stopNetScan() {
          try {
              mBinder.stopNetScan();
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
          }
      }
  
      /**
       * Start an energy scan.
       *
       * <p>This method will return once the scan has started.
       *
       * @see #stopEnergyScan
       */
      public void startEnergyScan() throws LowpanException {
          Map<String, Object> map = createScanOptionMap();
  
          ILowpanEnergyScanCallback binderListener =
                  new ILowpanEnergyScanCallback.Stub() {
                      public void onEnergyScanResult(int channel, int rssi) {
                          Callback callback = mCallback;
                          Handler handler = mHandler;
  
                          if (callback == null) {
                              return;
                          }
  
                          Runnable runnable =
                                  () -> {
                                      if (callback != null) {
                                          LowpanEnergyScanResult result =
                                                  new LowpanEnergyScanResult();
                                          result.setChannel(channel);
                                          result.setMaxRssi(rssi);
                                          callback.onEnergyScanResult(result);
                                      }
                                  };
  
                          if (handler != null) {
                              handler.post(runnable);
                          } else {
                              runnable.run();
                          }
                      }
  
                      public void onEnergyScanFinished() {
                          Callback callback = mCallback;
                          Handler handler = mHandler;
  
                          if (callback == null) {
                              return;
                          }
  
                          Runnable runnable = () -> callback.onScanFinished();
  
                          if (handler != null) {
                              handler.post(runnable);
                          } else {
                              runnable.run();
                          }
                      }
                  };
  
          try {
              mBinder.startEnergyScan(map, binderListener);
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      /**
       * Stop an energy scan currently in progress.
       *
       * @see #startEnergyScan
       */
      public void stopEnergyScan() {
          try {
              mBinder.stopEnergyScan();
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
          }
      }
  }
  