/**
   * Starts the session with the remote controllable device
   *
   * @param eventsListener
   */
  public void startSession(DeviceEventsListener eventsListener) throws ControlPanelException {
    if (eventsListener == null) {
      throw new ControlPanelException("Events listener can't be NULL");
    }

    deviceEventsListener = eventsListener;

    if (sessionId != null) {
      String msg =
          "The device is already in session: '" + deviceId + "', sessionId: '" + sessionId + "'";
      Log.d(TAG, msg);
      deviceEventsListener.sessionEstablished(this, getControlPanelCollections());
      return;
    }

    connMgr.registerEventListener(ConnManagerEventType.SESSION_JOINED, this);
    connMgr.registerEventListener(ConnManagerEventType.SESSION_LOST, this);
    connMgr.registerEventListener(ConnManagerEventType.SESSION_JOIN_FAIL, this);

    Log.d(TAG, "Device: '" + deviceId + "' starting session with sender: '" + sender + "'");
    Status status = connMgr.joinSession(sender, deviceId);

    if (status != Status.OK) {
      String statusName = status.name();
      Log.e(TAG, "Failed to join session: '" + statusName + "'");
      deviceEventsListener.errorOccurred(this, statusName);
      return;
    }
  } // startSession
  /**
   * Handle session join failed
   *
   * @param args
   */
  private void handleSessionJoinFailed(Map<String, Object> args) {
    String deviceId = (String) args.get("DEVICE_ID");
    Object statusObj = args.get("STATUS");

    if (statusObj == null || !(statusObj instanceof Status)) {
      return;
    }

    String status = ((Status) statusObj).name();

    Log.w(
        TAG,
        "Received SESSION_JOIN_FAIL event for deviceId: '"
            + deviceId
            + "', this deviceId is: '"
            + this.deviceId
            + "', Status: '"
            + status
            + "'");
    if (deviceId == null || !deviceId.equals(this.deviceId)) {
      return;
    }

    this.sessionId = null;

    connMgr.unregisterEventListener(ConnManagerEventType.SESSION_JOINED, this);
    connMgr.unregisterEventListener(ConnManagerEventType.SESSION_LOST, this);
    connMgr.unregisterEventListener(ConnManagerEventType.SESSION_JOIN_FAIL, this);

    deviceEventsListener.errorOccurred(this, status);
  } // handleSessionJoinedFailed
  /**
   * Stops all the device activities <br>
   * close session <br>
   * stop find adv name <br>
   * stop scheduled service <br>
   * Set is reachable to false
   */
  void stopDeviceActivities() {
    isReachable.set(false);

    try {
      connMgr.cancelFindAdvertisedName(sender);
    } catch (ControlPanelException cpe) {
      Log.e(TAG, "Failed to call cancelFindAdvertisedName(), Error: '" + cpe.getMessage() + "'");
    }

    connMgr.unregisterEventListener(ConnManagerEventType.FOUND_DEVICE, this);
    connMgr.unregisterEventListener(ConnManagerEventType.LOST_DEVICE, this);

    Status status = endSession();
    if (status != Status.OK) {
      Log.e(TAG, "Failed to end the session, Status: '" + status + "'");
    }

    stopDeviceFoundVerificationService();

    for (Unit unit : unitMap.values()) {
      unit.release();
    }

    unitMap.clear();
  } // stopDeviceActivities
  /**
   * Starts {@link ControlPanelService} which discovers controllable devices in its proximity. <br>
   * The ControlPanelService user is informed about the devices in proximity via the {@link
   * DeviceRegistry} interface. <br>
   * The discovery mechanism is implemented by receiving Announcement signals. <br>
   *
   * @param bus BusAttachment that the service should use
   * @param deviceRegistry Holds the information about the devices in proximity<br>
   *     {@link DefaultDeviceRegistry} may be passed in to receive information about the devices
   * @throws ControlPanelException if failed to initialize the control panel service
   */
  public void init(BusAttachment bus, DeviceRegistry deviceRegistry) throws ControlPanelException {
    if (deviceRegistry == null) {
      throw new ControlPanelException("deviceRegistry can't be NULL");
    }

    AboutService aboutService = AboutServiceImpl.getInstance();
    if (!aboutService.isClientRunning()) {
      throw new ControlPanelException(
          "The AboutService is not running, impossible to receive Announcement signals");
    }

    // Perform the basic service initialization
    init(bus);

    this.deviceRegistry = deviceRegistry;

    Log.d(TAG, "Start listening for Announcement signals");
    connMgr.registerEventListener(ConnManagerEventType.ANNOUNCEMENT_RECEIVED, this);

    // Add an announcement handler
    announcementReceiver = new AnnouncementReceiver();
    for (String iface : ANNOUNCEMENT_IFACES) {
      aboutService.addAnnouncementHandler(announcementReceiver, new String[] {iface});
    }
  } // init
  /** Shutdown the {@link ControlPanelService} */
  public void shutdown() {
    Log.d(TAG, "Shutdown ControlPanelService");

    if (announcementReceiver != null) {

      AboutService aboutService = AboutServiceImpl.getInstance();
      for (String iface : ANNOUNCEMENT_IFACES) {
        aboutService.removeAnnouncementHandler(announcementReceiver, new String[] {iface});
      }
    }

    if (deviceRegistry != null) {
      Log.d(TAG, "Clear devices registry");
      for (ControllableDevice device : deviceRegistry.getDevices().values()) {
        stopControllableDevice(device);
      }
      deviceRegistry = null;
    }

    TaskManager taskManager = TaskManager.getInstance();
    if (taskManager.isRunning()) {
      taskManager.shutdown();
    }

    connMgr.shutdown();
  } // shutdown
  /**
   * Constructor
   *
   * @param deviceId The device unique identifier
   * @param sender The unique identifier of the remote device
   */
  public ControllableDevice(String deviceId, String sender) {
    this.deviceId = deviceId;
    this.sender = sender;
    this.isReachable = new AtomicBoolean(false);
    this.unitMap = new HashMap<String, Unit>();

    this.sessionId = null;
    this.deviceRegistry = ControlPanelService.getInstance().getDeviceRegistry();
    this.connMgr = ConnectionManager.getInstance();
  } // Constructor
  /**
   * Handle session lost
   *
   * @param args
   */
  private void handleSessionLost(Map<String, Object> args) {
    Integer sessionId = (Integer) args.get("SESSION_ID");

    Log.w(
        TAG,
        "Received SESSION_LOST event for sessionId: '"
            + sessionId
            + "', this device sessionId is: '"
            + this.sessionId
            + "'");

    if (sessionId == null || !sessionId.equals(this.sessionId)) {
      return;
    }

    this.sessionId = null;

    connMgr.unregisterEventListener(ConnManagerEventType.SESSION_JOINED, this);
    connMgr.unregisterEventListener(ConnManagerEventType.SESSION_LOST, this);
    connMgr.unregisterEventListener(ConnManagerEventType.SESSION_JOIN_FAIL, this);

    deviceEventsListener.sessionLost(this);
  } // handleSessionLost
  /**
   * End the session with the remote controllable device
   *
   * @return {@link Status} of endSession execution
   */
  public Status endSession() {
    Log.i(TAG, "endSession has been called, leaving the session");

    if (sessionId == null) {
      Log.w(TAG, "Fail to execute endSession, sessionId is NULL, returning Status of FAIL");
      return Status.FAIL;
    }

    Status status;
    try {
      status = connMgr.leaveSession(sessionId);
    } catch (ControlPanelException cpe) {
      Log.e(
          TAG,
          "Failed to call leaveSession, Error: '"
              + cpe.getMessage()
              + "', returning status of FAIL");
      return Status.FAIL;
    }

    String logMsg = "endSession return Status is: '" + status + "'";

    if (status == Status.OK) {
      sessionId = null;
      Log.i(TAG, logMsg);

      // Unregister the session relevant events
      connMgr.unregisterEventListener(ConnManagerEventType.SESSION_JOINED, this);
      connMgr.unregisterEventListener(ConnManagerEventType.SESSION_LOST, this);
      connMgr.unregisterEventListener(ConnManagerEventType.SESSION_JOIN_FAIL, this);
    } else {
      Log.w(TAG, logMsg);
    }

    return status;
  } // endSession
 /** Subscribe to receive foundAdvName and lostAdvName events of ConnectionManager */
 void subscribeOnFoundLostEvents() {
   Log.d(TAG, "Register on ConnManager to receive events of found and lost advertised name");
   connMgr.registerEventListener(ConnManagerEventType.FOUND_DEVICE, this);
   connMgr.registerEventListener(ConnManagerEventType.LOST_DEVICE, this);
 } // subscribeOnFoundLostEvents
 /** Constructor */
 private ControlPanelService() {
   connMgr = ConnectionManager.getInstance();
 }
  /**
   * Checks whether the received announcement has ControlPanel interface <br>
   * If has creates ControllableObject and save it in the registry
   *
   * @param args
   */
  private void handleAnnouncement(Map<String, Object> args) {

    String deviceId = (String) args.get("DEVICE_ID");
    String appId = (String) args.get("APP_ID");
    String sender = (String) args.get("SENDER");

    BusObjectDescription[] objDescList = (BusObjectDescription[]) args.get("OBJ_DESC");

    if (deviceId == null || deviceId.length() == 0) {
      Log.e(TAG, "Received a bad Announcement signal, deviceId can't be NULL or empty");
      return;
    }
    if (sender == null || sender.length() == 0) {
      Log.e(TAG, "Received a bad Announcement signal, sender can't be NULL or empty");
      return;
    }
    if (objDescList == null || objDescList.length == 0) {
      Log.e(TAG, "Received a bad Announcement signal, BusObjectDescription array is empty");
      return;
    }

    // The controllable device id should be constructed from the deviceId and the appId
    deviceId = deviceId + "_" + appId;

    boolean newDevice = false; // TRUE if it's a not registered new device
    boolean handledDevice =
        false; // TRUE if for at least one received control panel object path we handled the device

    ControllableDevice device = deviceRegistry.getDevices().get(deviceId);

    // Iterate over the BusObjectDescription objects received from an Announcement signal
    for (BusObjectDescription busObjDesc : objDescList) {
      Log.v(TAG, "Found objPath: '" + busObjDesc.getPath() + "'");

      String[] interfaces = busObjDesc.getInterfaces();

      int ifaceMask = CommunicationUtil.getInterfaceMask(interfaces);
      // Check if found a ControlPanel or HTTPControl interfaces
      if (!CommunicationUtil.maskIncludes(ifaceMask, ControlPanel.ID_MASK)
          && !CommunicationUtil.maskIncludes(ifaceMask, HTTPControl.ID_MASK)) {
        continue;
      }

      String objPath = busObjDesc.getPath();

      Log.d(TAG, "Found ControlPanel object, path: '" + objPath + "'");

      if (!handledDevice) {

        if (device == null) {
          Log.d(
              TAG, "Discovered new device, deviceId: '" + deviceId + "', sender: '" + sender + "'");

          device = new ControllableDevice(deviceId, sender);
          device.subscribeOnFoundLostEvents(); // Listen to events of found | lost adv. name
          newDevice = true;
        } // device == null
        else {
          Log.d(
              TAG,
              "Device with deviceId: '"
                  + deviceId
                  + "' already exists, updating sender to be: '"
                  + sender
                  + "'");
          device.setSender(sender);

          try {
            connMgr.cancelFindAdvertisedName(sender);
          } catch (ControlPanelException cpe) {
            Log.e(
                TAG,
                "Failed to call cancelFindAdvertisedName(), Error: '" + cpe.getMessage() + "'");
            return;
          }
        } // else :: device == null

        device.setReachable(true);
        device.startDeviceFoundVerificationService(); // Start scheduled service before call
        // findAdvName

        Log.d(TAG, "Start findAdvertisedName for sender: '" + sender + "'");
        Status res;

        try {
          res = connMgr.findAdvertisedName(sender);
        } catch (ControlPanelException cpe) {
          Log.e(TAG, "Failed to call findAdvertisedName(), Error: '" + cpe.getMessage() + "'");
          return;
        }

        if (res != Status.OK) {
          Log.d(
              TAG,
              "Failed to start findAdvertisedName for sender: '"
                  + sender
                  + "', Error: '"
                  + res
                  + "'");
          device.stopDeviceActivities();
          return;
        }

        // We handled the discovered device for at least one of the received ControlPanel object
        // paths
        handledDevice = true;
      } // if :: not handledDevice

      try {
        device.addControlPanel(objPath, ifaceMask);
      } catch (ControlPanelException cpe) {
        Log.w(
            TAG,
            "Received a broken object path: '" + objPath + "', Error: '" + cpe.getMessage() + "'");
      }
    } // for :: BusObjectDescription

    if (handledDevice) {
      if (newDevice) {
        deviceRegistry.foundNewDevice(device);
      } else {
        deviceRegistry.reachabilityChanged(device, true);
      }
    }
  } // handleAnnouncement
 /**
  * Starts {@link ControlPanelService} without discovering new devices in proximity
  *
  * @param bus
  */
 public void init(BusAttachment bus) throws ControlPanelException {
   connMgr.setBusAttachment(bus);
   TaskManager.getInstance().initPool();
 } // init