content: /*******************************************************************************
   * Software Name : RCS IMS Stack
   *
   * Copyright (C) 2010-2016 Orange.
   * Copyright (C) 2015 Sony Mobile Communications AB.
   *
   * 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.
   * 
   * NOTE: This file has been modified by Sony Mobile Communications AB.
   * Modifications are licensed under the License.
   ******************************************************************************/
  
  package com.gsma.rcs.core.ims.service.capability;
  
  import com.gsma.rcs.core.FileAccessException;
  import com.gsma.rcs.core.ims.ImsModule;
  import com.gsma.rcs.core.ims.network.NetworkException;
  import com.gsma.rcs.core.ims.network.sip.SipMessageFactory;
  import com.gsma.rcs.core.ims.network.sip.SipUtils;
  import com.gsma.rcs.core.ims.protocol.PayloadException;
  import com.gsma.rcs.core.ims.protocol.sip.SipRequest;
  import com.gsma.rcs.core.ims.protocol.sip.SipResponse;
  import com.gsma.rcs.core.ims.service.ContactInfo.RcsStatus;
  import com.gsma.rcs.core.ims.service.ContactInfo.RegistrationState;
  import com.gsma.rcs.core.ims.service.capability.OptionsRequestTask.IOptionsRequestTaskListener;
  import com.gsma.rcs.provider.contact.ContactManager;
  import com.gsma.rcs.provider.contact.ContactManagerException;
  import com.gsma.rcs.provider.settings.RcsSettings;
  import com.gsma.rcs.utils.ContactUtil;
  import com.gsma.rcs.utils.ContactUtil.PhoneNumber;
  import com.gsma.rcs.utils.logger.Logger;
  import com.gsma.services.rcs.contact.ContactId;
  
  import java.util.HashSet;
  import java.util.Set;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  
  /**
   * Capability discovery manager using options procedure
   * 
   * @author jexa7410
   */
  public class OptionsManager implements DiscoveryManager {
      /**
       * Max number of threads for background processing
       */
      private final static int MAX_PROCESSING_THREADS = 15;
  
      private ImsModule mImsModule;
  
      /**
       * Thread pool to request capabilities in background
       */
      private ExecutorService mThreadPool;
  
      private final RcsSettings mRcsSettings;
  
      private final ContactManager mContactManager;
  
      private final static Logger sLogger = Logger.getLogger(OptionsManager.class.getSimpleName());
  
      /**
       * Constructor
       * 
       * @param parent IMS module
       * @param rcsSettings RCS settings accessor
       * @param contactManager Contact manager accessor
       */
      public OptionsManager(ImsModule parent, RcsSettings rcsSettings, ContactManager contactManager) {
          mImsModule = parent;
          mRcsSettings = rcsSettings;
          mContactManager = contactManager;
      }
  
      /**
       * Start the manager
       */
      public void start() {
          mThreadPool = Executors.newFixedThreadPool(MAX_PROCESSING_THREADS);
      }
  
      /**
       * Stop the manager
       */
      public void stop() {
          mThreadPool.shutdownNow();
      }
  
      /**
       * Request capabilities in background
       * 
       * @param contact Contact ID
       * @param listener callback to execute when response is received
       */
      private void requestCapabilitiesInBackground(ContactId contact,
              IOptionsRequestTaskListener listener) {
          if (sLogger.isActivated()) {
              sLogger.debug("Request capabilities in background for ".concat(contact.toString()));
          }
          boolean richcall = mImsModule.getRichcallService().isCallConnectedWith(contact);
          OptionsRequestTask task = new OptionsRequestTask(mImsModule, contact,
                  CapabilityUtils.getSupportedFeatureTags(richcall, mRcsSettings), mRcsSettings,
                  mContactManager, listener);
          if (mThreadPool.isShutdown()) {
              if (sLogger.isActivated()) {
                  sLogger.warn("Request capabilities in background for " + contact
                          + " rejected: manager is stopped!");
              }
              return;
          }
          mThreadPool.submit(task);
      }
  
      /**
       * Check if refresh of capability is authorized
       * 
       * @param timestampOfLastRequest timestamp of last capability request in milliseconds
       * @return true if capability request is authorized
       */
      private boolean isCapabilityRefreshAuthorized(long timestampOfLastRequest) {
          long currentTime = System.currentTimeMillis();
          /*
           * Is current time before last capability request ? (may occur if current time has been
           * updated)
           */
          if (currentTime < timestampOfLastRequest) {
              return true;
          }
          return (currentTime > (timestampOfLastRequest + mRcsSettings.getCapabilityRefreshTimeout()));
      }
  
      /**
       * Request contact capabilities
       * 
       * @param contact Remote contact identifier
       */
      public void requestCapabilities(ContactId contact) {
          boolean logActivated = sLogger.isActivated();
          if (contact == null || contact.equals(ImsModule.getImsUserProfile().getUsername())) {
              return;
          }
          Capabilities capabilities = mContactManager.getContactCapabilities(contact);
          if (capabilities == null) {
              if (logActivated) {
                  sLogger.debug("No capability exist for ".concat(contact.toString()));
              }
              requestCapabilitiesInBackground(contact, null);
              mContactManager.updateCapabilitiesTimeLastRequest(contact);
          } else {
              if (logActivated) {
                  sLogger.debug("Capabilities exist for ".concat(contact.toString()));
              }
              if (isCapabilityRefreshAuthorized(capabilities.getTimestampOfLastRequest())) {
                  if (logActivated) {
                      sLogger.debug("Request capabilities for ".concat(contact.toString()));
                  }
                  requestCapabilitiesInBackground(contact, null);
                  mContactManager.updateCapabilitiesTimeLastRequest(contact);
              }
          }
      }
  
      /**
       * Request capabilities for a set of contacts
       * 
       * @param contacts Contact set
       */
      public void requestCapabilities(Set<ContactId> contacts) {
          for (ContactId contact : contacts) {
              requestCapabilities(contact);
          }
      }
  
      /**
       * Receive a capability request (options procedure)
       * 
       * @param options Received options message
       * @throws PayloadException
       * @throws NetworkException
       * @throws ContactManagerException
       */
      public void onCapabilityRequestReceived(SipRequest options) throws PayloadException,
              NetworkException, ContactManagerException {
          String sipId = SipUtils.getAssertedIdentity(options);
          try {
              PhoneNumber number = ContactUtil.getValidPhoneNumberFromUri(sipId);
              if (number == null) {
                  if (sLogger.isActivated()) {
                      sLogger.debug(new StringBuilder("Invalid contact from capability request '")
                              .append(sipId).append("'").toString());
                  }
                  return;
              }
              ContactId contact = ContactUtil.createContactIdFromValidatedData(number);
              if (sLogger.isActivated()) {
                  sLogger.debug("OPTIONS request received from ".concat(contact.toString()));
              }
  
              // Create 200 OK response
              String ipAddress = mImsModule.getCurrentNetworkInterface().getNetworkAccess()
                      .getIpAddress();
              boolean richcall = mImsModule.getRichcallService().isCallConnectedWith(contact);
              SipResponse resp = SipMessageFactory.create200OkOptionsResponse(options, mImsModule
                      .getSipManager().getSipStack().getContact(),
                      CapabilityUtils.getSupportedFeatureTags(richcall, mRcsSettings),
                      CapabilityUtils.buildSdp(ipAddress, richcall, mRcsSettings));
  
              // Send 200 OK response
              mImsModule.getSipManager().sendSipResponse(resp);
              // Read features tag in the request
              Capabilities capabilities = CapabilityUtils.extractCapabilities(options);
  
              // Update capabilities in database
              if (capabilities.isImSessionSupported()) {
                  // RCS-e contact
                  mContactManager.setContactCapabilities(contact, capabilities,
                          RcsStatus.RCS_CAPABLE, RegistrationState.ONLINE);
              } else {
                  // Not a RCS-e contact
                  mContactManager.setContactCapabilities(contact, capabilities, RcsStatus.NOT_RCS,
                          RegistrationState.UNKNOWN);
              }
  
              // Notify listener
              mImsModule.getCapabilityService().onReceivedCapabilities(contact, capabilities);
  
          } catch (FileAccessException e) {
              throw new PayloadException(new StringBuilder("Failed to receive capability request '")
                      .append(sipId).append("'").toString(), e);
          }
      }
  
      /**
       * Requests capabilities for a set of contacts
       * 
       * @param contacts Set of contacts to query.
       * @param callback Callback to execute once all contacts have been queried or null if caller
       *            does need to be notified
       */
      public void requestCapabilities(Set<ContactId> contacts, final IOptionsManagerListener callback) {
          IOptionsRequestTaskListener listener = null;
          final Set<ContactId> contactsToQuery = new HashSet<ContactId>(contacts);
          if (callback != null) {
              listener = new IOptionsRequestTaskListener() {
  
                  @Override
                  public void endOfOptionsRequestTask(ContactId contact) {
                      synchronized (contactsToQuery) {
                          contactsToQuery.remove(contact);
                          if (contactsToQuery.isEmpty()) {
                              callback.endOfCapabilitiesRequest();
                          }
                      }
                  }
              };
          }
          for (ContactId contact : contacts) {
              requestCapabilitiesInBackground(contact, listener);
          }
      }
  
      /**
       * Interface listener for OptionsManager
       */
      public interface IOptionsManagerListener {
          /**
           * Callback to notify end of capabilities request
           */
          public void endOfCapabilitiesRequest();
      }
  }
  