content: /*
   * Copyright (c) 2013 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 com.android.ims;
  
  import java.util.HashMap;
  import java.util.Map;
  
  import android.content.res.Resources;
  import android.os.AsyncResult;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.Message;
  import android.os.Registrant;
  import android.os.RemoteException;
  import android.telephony.Rlog;
  import android.telephony.ims.ImsCallForwardInfo;
  import android.telephony.ims.ImsReasonInfo;
  import android.telephony.ims.ImsSsData;
  import android.telephony.ims.ImsSsInfo;
  
  import com.android.ims.internal.IImsUt;
  import com.android.ims.internal.IImsUtListener;
  
  /**
   * Provides APIs for the supplementary service settings using IMS (Ut interface).
   * It is created from 3GPP TS 24.623 (XCAP(XML Configuration Access Protocol)
   * over the Ut interface for manipulating supplementary services).
   *
   * @hide
   */
  public class ImsUt implements ImsUtInterface {
      /**
       * Key string for an additional supplementary service configurations.
       */
      /**
       * Actions : string format of ImsUtInterface#ACTION_xxx
       *      "0" (deactivation), "1" (activation), "2" (not_used),
       *      "3" (registration), "4" (erasure), "5" (Interrogation)
       */
      public static final String KEY_ACTION = "action";
      /**
       * Categories :
       *      "OIP", "OIR", "TIP", "TIR", "CDIV", "CB", "CW", "CONF",
       *      "ACR", "MCID", "ECT", "CCBS", "AOC", "MWI", "FA", "CAT"
       *
       * Detailed parameter name will be determined according to the properties
       * of the supplementary service configuration.
       */
      public static final String KEY_CATEGORY = "category";
      public static final String CATEGORY_OIP = "OIP";
      public static final String CATEGORY_OIR = "OIR";
      public static final String CATEGORY_TIP = "TIP";
      public static final String CATEGORY_TIR = "TIR";
      public static final String CATEGORY_CDIV = "CDIV";
      public static final String CATEGORY_CB = "CB";
      public static final String CATEGORY_CW = "CW";
      public static final String CATEGORY_CONF = "CONF";
  
      private static final String TAG = "ImsUt";
      private static final boolean DBG = true;
  
      //These service class values are same as the one in CommandsInterface.java
      private static final int SERVICE_CLASS_NONE = 0;
      private static final int SERVICE_CLASS_VOICE = (1 << 0);
  
      // For synchronization of private variables
      private Object mLockObj = new Object();
      private final IImsUt miUt;
      private HashMap<Integer, Message> mPendingCmds =
              new HashMap<Integer, Message>();
      private Registrant mSsIndicationRegistrant;
  
      public ImsUt(IImsUt iUt) {
          miUt = iUt;
  
          if (miUt != null) {
              try {
                  miUt.setListener(new IImsUtListenerProxy());
              } catch (RemoteException e) {
              }
          }
      }
  
      public void close() {
          synchronized(mLockObj) {
              if (miUt != null) {
                  try {
                      miUt.close();
                  } catch (RemoteException e) {
                  }
              }
  
              if (!mPendingCmds.isEmpty()) {
                  Map.Entry<Integer, Message>[] entries =
                      mPendingCmds.entrySet().toArray(new Map.Entry[mPendingCmds.size()]);
  
                  for (Map.Entry<Integer, Message> entry : entries) {
                      sendFailureReport(entry.getValue(),
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                  }
  
                  mPendingCmds.clear();
              }
          }
      }
  
      /**
       * Registers a handler for Supplementary Service Indications. The
       * result is returned in the {@link AsyncResult#result) field
       * of the {@link AsyncResult} object returned by {@link Message.obj}.
       * Value of ((AsyncResult)result.obj) is of {@link ImsSsData}.
       */
      public void registerForSuppServiceIndication(Handler h, int what, Object obj) {
          mSsIndicationRegistrant = new Registrant (h, what, obj);
      }
  
      /**
       * UnRegisters a handler for Supplementary Service Indications.
       */
      public void unregisterForSuppServiceIndication(Handler h) {
          mSsIndicationRegistrant.clear();
      }
  
      /**
       * Operations for the supplementary service configuration
       */
  
      /**
       * Retrieves the configuration of the call barring.
       *
       * @param cbType type of call barring to be queried; ImsUtInterface#CB_XXX
       * @param result message to pass the result of this operation
       *      The return value of ((AsyncResult)result.obj) is an array of {@link ImsSsInfo}.
       * @deprecated Use {@link #queryCallBarring(int, Message, int)} instead.
       */
      @Override
      public void queryCallBarring(int cbType, Message result) {
          queryCallBarring(cbType, result, SERVICE_CLASS_NONE);
      }
  
      /**
       * Retrieves the configuration of the call barring for specified service class.
       *
       * @param cbType type of call barring to be queried; ImsUtInterface#CB_XXX
       * @param result message to pass the result of this operation
       *      The return value of ((AsyncResult)result.obj) is an array of {@link ImsSsInfo}.
       * @param serviceClass service class for e.g. voice/video
       */
      @Override
      public void queryCallBarring(int cbType, Message result, int serviceClass) {
          if (DBG) {
              log("queryCallBarring :: Ut=" + miUt + ", cbType=" + cbType + ", serviceClass="
                      + serviceClass);
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.queryCallBarringForServiceClass(cbType, serviceClass);
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      /**
       * Retrieves the configuration of the call forward.
       * The return value of ((AsyncResult)result.obj) is an array of {@link ImsCallForwardInfo}.
       */
      @Override
      public void queryCallForward(int condition, String number, Message result) {
          if (DBG) {
              log("queryCallForward :: Ut=" + miUt + ", condition=" + condition
                      + ", number=" + Rlog.pii(TAG, number));
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.queryCallForward(condition, number);
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      /**
       * Retrieves the configuration of the call waiting.
       * The return value of ((AsyncResult)result.obj) is an array of {@link ImsSsInfo}.
       */
      @Override
      public void queryCallWaiting(Message result) {
          if (DBG) {
              log("queryCallWaiting :: Ut=" + miUt);
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.queryCallWaiting();
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      /**
       * Retrieves the default CLIR setting.
       */
      @Override
      public void queryCLIR(Message result) {
          if (DBG) {
              log("queryCLIR :: Ut=" + miUt);
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.queryCLIR();
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      /**
       * Retrieves the CLIP call setting.
       */
      public void queryCLIP(Message result) {
          if (DBG) {
              log("queryCLIP :: Ut=" + miUt);
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.queryCLIP();
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      /**
       * Retrieves the COLR call setting.
       */
      public void queryCOLR(Message result) {
          if (DBG) {
              log("queryCOLR :: Ut=" + miUt);
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.queryCOLR();
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      /**
       * Retrieves the COLP call setting.
       */
      public void queryCOLP(Message result) {
          if (DBG) {
              log("queryCOLP :: Ut=" + miUt);
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.queryCOLP();
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      /**
       * Modifies the configuration of the call barring.
       * @deprecated Use {@link #updateCallBarring(int, int, Message, String[], int)} instead.
       */
      @Override
      public void updateCallBarring(int cbType, int action, Message result, String[] barrList) {
          updateCallBarring(cbType, action, result, barrList, SERVICE_CLASS_NONE);
      }
  
      /**
       * Modifies the configuration of the call barring for specified service class.
       */
      @Override
      public void updateCallBarring(int cbType, int action, Message result,
              String[] barrList, int serviceClass) {
          if (DBG) {
              if (barrList != null) {
                  String bList = new String();
                  for (int i = 0; i < barrList.length; i++) {
                      bList.concat(barrList[i] + " ");
                  }
                  log("updateCallBarring :: Ut=" + miUt + ", cbType=" + cbType
                          + ", action=" + action + ", serviceClass=" + serviceClass
                          + ", barrList=" + bList);
              }
              else {
                  log("updateCallBarring :: Ut=" + miUt + ", cbType=" + cbType
                          + ", action=" + action + ", serviceClass=" + serviceClass);
              }
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.updateCallBarringForServiceClass(cbType, action,
                          barrList, serviceClass);
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      /**
       * Modifies the configuration of the call forward.
       */
      @Override
      public void updateCallForward(int action, int condition, String number,
              int serviceClass, int timeSeconds, Message result) {
          if (DBG) {
              log("updateCallForward :: Ut=" + miUt + ", action=" + action
                      + ", condition=" + condition + ", number=" + Rlog.pii(TAG, number)
                      + ", serviceClass=" + serviceClass + ", timeSeconds=" + timeSeconds);
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.updateCallForward(action, condition, number, serviceClass, timeSeconds);
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      /**
       * Modifies the configuration of the call waiting.
       */
      @Override
      public void updateCallWaiting(boolean enable, int serviceClass, Message result) {
          if (DBG) {
              log("updateCallWaiting :: Ut=" + miUt + ", enable=" + enable
              + ",serviceClass="  + serviceClass);
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.updateCallWaiting(enable, serviceClass);
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      /**
       * Updates the configuration of the CLIR supplementary service.
       */
      @Override
      public void updateCLIR(int clirMode, Message result) {
          if (DBG) {
              log("updateCLIR :: Ut=" + miUt + ", clirMode=" + clirMode);
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.updateCLIR(clirMode);
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      /**
       * Updates the configuration of the CLIP supplementary service.
       */
      @Override
      public void updateCLIP(boolean enable, Message result) {
          if (DBG) {
              log("updateCLIP :: Ut=" + miUt + ", enable=" + enable);
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.updateCLIP(enable);
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      /**
       * Updates the configuration of the COLR supplementary service.
       */
      @Override
      public void updateCOLR(int presentation, Message result) {
          if (DBG) {
              log("updateCOLR :: Ut=" + miUt + ", presentation=" + presentation);
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.updateCOLR(presentation);
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      /**
       * Updates the configuration of the COLP supplementary service.
       */
      @Override
      public void updateCOLP(boolean enable, Message result) {
          if (DBG) {
              log("updateCallWaiting :: Ut=" + miUt + ", enable=" + enable);
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.updateCOLP(enable);
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      /**
       * @return returns true if the binder is alive, false otherwise.
       */
      public boolean isBinderAlive() {
          return miUt.asBinder().isBinderAlive();
      }
  
      public void transact(Bundle ssInfo, Message result) {
          if (DBG) {
              log("transact :: Ut=" + miUt + ", ssInfo=" + ssInfo);
          }
  
          synchronized(mLockObj) {
              try {
                  int id = miUt.transact(ssInfo);
  
                  if (id < 0) {
                      sendFailureReport(result,
                              new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
                      return;
                  }
  
                  mPendingCmds.put(Integer.valueOf(id), result);
              } catch (RemoteException e) {
                  sendFailureReport(result,
                          new ImsReasonInfo(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, 0));
              }
          }
      }
  
      private void sendFailureReport(Message result, ImsReasonInfo error) {
          if (result == null || error == null) {
              return;
          }
  
          String errorString;
          // If ImsReasonInfo object does not have a String error code, use a
          // default error string.
          if (error.mExtraMessage == null) {
              errorString = Resources.getSystem().getString(
                      com.android.internal.R.string.mmiError);
          }
          else {
              errorString = new String(error.mExtraMessage);
          }
          AsyncResult.forMessage(result, null, new ImsException(errorString, error.mCode));
          result.sendToTarget();
      }
  
      private void sendSuccessReport(Message result) {
          if (result == null) {
              return;
          }
  
          AsyncResult.forMessage(result, null, null);
          result.sendToTarget();
      }
  
      private void sendSuccessReport(Message result, Object ssInfo) {
          if (result == null) {
              return;
          }
  
          AsyncResult.forMessage(result, ssInfo, null);
          result.sendToTarget();
      }
  
      private void log(String s) {
          Rlog.d(TAG, s);
      }
  
      private void loge(String s) {
          Rlog.e(TAG, s);
      }
  
      private void loge(String s, Throwable t) {
          Rlog.e(TAG, s, t);
      }
  
      /**
       * A listener type for the result of the supplementary service configuration.
       */
      private class IImsUtListenerProxy extends IImsUtListener.Stub {
          /**
           * Notifies the result of the supplementary service configuration udpate.
           */
          @Override
          public void utConfigurationUpdated(IImsUt ut, int id) {
              Integer key = Integer.valueOf(id);
  
              synchronized(mLockObj) {
                  sendSuccessReport(mPendingCmds.get(key));
                  mPendingCmds.remove(key);
              }
          }
  
          @Override
          public void utConfigurationUpdateFailed(IImsUt ut, int id, ImsReasonInfo error) {
              Integer key = Integer.valueOf(id);
  
              synchronized(mLockObj) {
                  sendFailureReport(mPendingCmds.get(key), error);
                  mPendingCmds.remove(key);
              }
          }
  
          /**
           * Notifies the result of the supplementary service configuration query.
           */
          @Override
          public void utConfigurationQueried(IImsUt ut, int id, Bundle ssInfo) {
              Integer key = Integer.valueOf(id);
  
              synchronized(mLockObj) {
                  sendSuccessReport(mPendingCmds.get(key), ssInfo);
                  mPendingCmds.remove(key);
              }
          }
  
          @Override
          public void utConfigurationQueryFailed(IImsUt ut, int id, ImsReasonInfo error) {
              Integer key = Integer.valueOf(id);
  
              synchronized(mLockObj) {
                  sendFailureReport(mPendingCmds.get(key), error);
                  mPendingCmds.remove(key);
              }
          }
  
          /**
           * Notifies the status of the call barring supplementary service.
           */
          @Override
          public void utConfigurationCallBarringQueried(IImsUt ut,
                  int id, ImsSsInfo[] cbInfo) {
              Integer key = Integer.valueOf(id);
  
              synchronized(mLockObj) {
                  sendSuccessReport(mPendingCmds.get(key), cbInfo);
                  mPendingCmds.remove(key);
              }
          }
  
          /**
           * Notifies the status of the call forwarding supplementary service.
           */
          @Override
          public void utConfigurationCallForwardQueried(IImsUt ut,
                  int id, ImsCallForwardInfo[] cfInfo) {
              Integer key = Integer.valueOf(id);
  
              synchronized(mLockObj) {
                  sendSuccessReport(mPendingCmds.get(key), cfInfo);
                  mPendingCmds.remove(key);
              }
          }
  
          /**
           * Notifies the status of the call waiting supplementary service.
           */
          @Override
          public void utConfigurationCallWaitingQueried(IImsUt ut,
                  int id, ImsSsInfo[] cwInfo) {
              Integer key = Integer.valueOf(id);
  
              synchronized(mLockObj) {
                  sendSuccessReport(mPendingCmds.get(key), cwInfo);
                  mPendingCmds.remove(key);
              }
          }
  
          /**
           * Notifies client when Supplementary Service indication is received
           */
          @Override
          public void onSupplementaryServiceIndication(ImsSsData ssData) {
              if (mSsIndicationRegistrant != null) {
                  mSsIndicationRegistrant.notifyResult(ssData);
              }
          }
      }
  }
  