content: /*
   * Copyright (C) 2007 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.internal.telephony.cat;
  
  import android.annotation.UnsupportedAppUsage;
  import android.graphics.Bitmap;
  import android.os.Handler;
  import android.os.Message;
  import android.text.TextUtils;
  
  import com.android.internal.telephony.GsmAlphabet;
  import com.android.internal.telephony.uicc.IccFileHandler;
  
  import java.util.Iterator;
  import java.util.List;
  import java.util.Locale;
  
  import static com.android.internal.telephony.cat.CatCmdMessage
                     .SetupEventListConstants.BROWSER_TERMINATION_EVENT;
  import static com.android.internal.telephony.cat.CatCmdMessage
                     .SetupEventListConstants.BROWSING_STATUS_EVENT;
  import static com.android.internal.telephony.cat.CatCmdMessage
                     .SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
  import static com.android.internal.telephony.cat.CatCmdMessage
                     .SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
  import static com.android.internal.telephony.cat.CatCmdMessage
                     .SetupEventListConstants.USER_ACTIVITY_EVENT;
  /**
   * Factory class, used for decoding raw byte arrays, received from baseband,
   * into a CommandParams object.
   *
   */
  class CommandParamsFactory extends Handler {
      private static CommandParamsFactory sInstance = null;
      @UnsupportedAppUsage
      private IconLoader mIconLoader;
      private CommandParams mCmdParams = null;
      private int mIconLoadState = LOAD_NO_ICON;
      private RilMessageDecoder mCaller = null;
      private boolean mloadIcon = false;
      private String mSavedLanguage;
      private String mRequestedLanguage;
  
      // constants
      static final int MSG_ID_LOAD_ICON_DONE = 1;
  
      // loading icons state parameters.
      static final int LOAD_NO_ICON           = 0;
      static final int LOAD_SINGLE_ICON       = 1;
      static final int LOAD_MULTI_ICONS       = 2;
  
      // Command Qualifier values for PLI command
      static final int DTTZ_SETTING                           = 0x03;
      static final int LANGUAGE_SETTING                       = 0x04;
  
      // Command Qualifier value for language notification command
      static final int NON_SPECIFIC_LANGUAGE                  = 0x00;
      static final int SPECIFIC_LANGUAGE                      = 0x01;
  
      // As per TS 102.223 Annex C, Structure of CAT communications,
      // the APDU length can be max 255 bytes. This leaves only 239 bytes for user
      // input string. CMD details TLV + Device IDs TLV + Result TLV + Other
      // details of TextString TLV not including user input take 16 bytes.
      //
      // If UCS2 encoding is used, maximum 118 UCS2 chars can be encoded in 238 bytes.
      // Each UCS2 char takes 2 bytes. Byte Order Mask(BOM), 0xFEFF takes 2 bytes.
      //
      // If GSM 7 bit default(use 8 bits to represent a 7 bit char) format is used,
      // maximum 239 chars can be encoded in 239 bytes since each char takes 1 byte.
      //
      // No issues for GSM 7 bit packed format encoding.
  
      private static final int MAX_GSM7_DEFAULT_CHARS = 239;
      private static final int MAX_UCS2_CHARS = 118;
  
      static synchronized CommandParamsFactory getInstance(RilMessageDecoder caller,
              IccFileHandler fh) {
          if (sInstance != null) {
              return sInstance;
          }
          if (fh != null) {
              return new CommandParamsFactory(caller, fh);
          }
          return null;
      }
  
      private CommandParamsFactory(RilMessageDecoder caller, IccFileHandler fh) {
          mCaller = caller;
          mIconLoader = IconLoader.getInstance(this, fh);
      }
  
      private CommandDetails processCommandDetails(List<ComprehensionTlv> ctlvs) {
          CommandDetails cmdDet = null;
  
          if (ctlvs != null) {
              // Search for the Command Details object.
              ComprehensionTlv ctlvCmdDet = searchForTag(
                      ComprehensionTlvTag.COMMAND_DETAILS, ctlvs);
              if (ctlvCmdDet != null) {
                  try {
                      cmdDet = ValueParser.retrieveCommandDetails(ctlvCmdDet);
                  } catch (ResultException e) {
                      CatLog.d(this,
                              "processCommandDetails: Failed to procees command details e=" + e);
                  }
              }
          }
          return cmdDet;
      }
  
      void make(BerTlv berTlv) {
          if (berTlv == null) {
              return;
          }
          // reset global state parameters.
          mCmdParams = null;
          mIconLoadState = LOAD_NO_ICON;
          // only proactive command messages are processed.
          if (berTlv.getTag() != BerTlv.BER_PROACTIVE_COMMAND_TAG) {
              sendCmdParams(ResultCode.CMD_TYPE_NOT_UNDERSTOOD);
              return;
          }
          boolean cmdPending = false;
          List<ComprehensionTlv> ctlvs = berTlv.getComprehensionTlvs();
          // process command dtails from the tlv list.
          CommandDetails cmdDet = processCommandDetails(ctlvs);
          if (cmdDet == null) {
              sendCmdParams(ResultCode.CMD_TYPE_NOT_UNDERSTOOD);
              return;
          }
  
          // extract command type enumeration from the raw value stored inside
          // the Command Details object.
          AppInterface.CommandType cmdType = AppInterface.CommandType
                  .fromInt(cmdDet.typeOfCommand);
          if (cmdType == null) {
              // This PROACTIVE COMMAND is presently not handled. Hence set
              // result code as BEYOND_TERMINAL_CAPABILITY in TR.
              mCmdParams = new CommandParams(cmdDet);
              sendCmdParams(ResultCode.BEYOND_TERMINAL_CAPABILITY);
              return;
          }
  
          // proactive command length is incorrect.
          if (!berTlv.isLengthValid()) {
              mCmdParams = new CommandParams(cmdDet);
              sendCmdParams(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
              return;
          }
  
          try {
              switch (cmdType) {
              case SET_UP_MENU:
                  cmdPending = processSelectItem(cmdDet, ctlvs);
                  break;
              case SELECT_ITEM:
                  cmdPending = processSelectItem(cmdDet, ctlvs);
                  break;
              case DISPLAY_TEXT:
                  cmdPending = processDisplayText(cmdDet, ctlvs);
                  break;
               case SET_UP_IDLE_MODE_TEXT:
                   cmdPending = processSetUpIdleModeText(cmdDet, ctlvs);
                   break;
               case GET_INKEY:
                  cmdPending = processGetInkey(cmdDet, ctlvs);
                  break;
               case GET_INPUT:
                   cmdPending = processGetInput(cmdDet, ctlvs);
                   break;
               case SEND_DTMF:
               case SEND_SMS:
               case REFRESH:
               case RUN_AT:
               case SEND_SS:
               case SEND_USSD:
                   cmdPending = processEventNotify(cmdDet, ctlvs);
                   break;
               case GET_CHANNEL_STATUS:
               case SET_UP_CALL:
                   cmdPending = processSetupCall(cmdDet, ctlvs);
                   break;
               case LAUNCH_BROWSER:
                   cmdPending = processLaunchBrowser(cmdDet, ctlvs);
                   break;
               case PLAY_TONE:
                  cmdPending = processPlayTone(cmdDet, ctlvs);
                  break;
               case SET_UP_EVENT_LIST:
                   cmdPending = processSetUpEventList(cmdDet, ctlvs);
                   break;
               case PROVIDE_LOCAL_INFORMATION:
                  cmdPending = processProvideLocalInfo(cmdDet, ctlvs);
                  break;
               case LANGUAGE_NOTIFICATION:
                   cmdPending = processLanguageNotification(cmdDet, ctlvs);
                   break;
               case OPEN_CHANNEL:
               case CLOSE_CHANNEL:
               case RECEIVE_DATA:
               case SEND_DATA:
                   cmdPending = processBIPClient(cmdDet, ctlvs);
                   break;
              default:
                  // unsupported proactive commands
                  mCmdParams = new CommandParams(cmdDet);
                  sendCmdParams(ResultCode.BEYOND_TERMINAL_CAPABILITY);
                  return;
              }
          } catch (ResultException e) {
              CatLog.d(this, "make: caught ResultException e=" + e);
              mCmdParams = new CommandParams(cmdDet);
              sendCmdParams(e.result());
              return;
          }
          if (!cmdPending) {
              sendCmdParams(ResultCode.OK);
          }
      }
  
      @Override
      public void handleMessage(Message msg) {
          switch (msg.what) {
          case MSG_ID_LOAD_ICON_DONE:
              if (mIconLoader != null) {
                  sendCmdParams(setIcons(msg.obj));
              }
              break;
          }
      }
  
      private ResultCode setIcons(Object data) {
          Bitmap[] icons = null;
          int iconIndex = 0;
  
          if (data == null) {
              CatLog.d(this, "Optional Icon data is NULL");
              mCmdParams.mLoadIconFailed = true;
              mloadIcon = false;
              /** In case of icon load fail consider the
              ** received proactive command as valid (sending RESULT OK) as
              ** The result code, 'PRFRMD_ICON_NOT_DISPLAYED' will be added in the
              ** terminal response by CatService/StkAppService if needed based on
              ** the value of mLoadIconFailed.
              */
              return ResultCode.OK;
          }
          switch(mIconLoadState) {
          case LOAD_SINGLE_ICON:
              mCmdParams.setIcon((Bitmap) data);
              break;
          case LOAD_MULTI_ICONS:
              icons = (Bitmap[]) data;
              // set each item icon.
              for (Bitmap icon : icons) {
                  mCmdParams.setIcon(icon);
                  if (icon == null && mloadIcon) {
                      CatLog.d(this, "Optional Icon data is NULL while loading multi icons");
                      mCmdParams.mLoadIconFailed = true;
                  }
              }
              break;
          }
          return ResultCode.OK;
      }
  
      private void sendCmdParams(ResultCode resCode) {
          mCaller.sendMsgParamsDecoded(resCode, mCmdParams);
      }
  
      /**
       * Search for a COMPREHENSION-TLV object with the given tag from a list
       *
       * @param tag A tag to search for
       * @param ctlvs List of ComprehensionTlv objects used to search in
       *
       * @return A ComprehensionTlv object that has the tag value of {@code tag}.
       *         If no object is found with the tag, null is returned.
       */
      @UnsupportedAppUsage
      private ComprehensionTlv searchForTag(ComprehensionTlvTag tag,
              List<ComprehensionTlv> ctlvs) {
          Iterator<ComprehensionTlv> iter = ctlvs.iterator();
          return searchForNextTag(tag, iter);
      }
  
      /**
       * Search for the next COMPREHENSION-TLV object with the given tag from a
       * list iterated by {@code iter}. {@code iter} points to the object next to
       * the found object when this method returns. Used for searching the same
       * list for similar tags, usually item id.
       *
       * @param tag A tag to search for
       * @param iter Iterator for ComprehensionTlv objects used for search
       *
       * @return A ComprehensionTlv object that has the tag value of {@code tag}.
       *         If no object is found with the tag, null is returned.
       */
      @UnsupportedAppUsage
      private ComprehensionTlv searchForNextTag(ComprehensionTlvTag tag,
              Iterator<ComprehensionTlv> iter) {
          int tagValue = tag.value();
          while (iter.hasNext()) {
              ComprehensionTlv ctlv = iter.next();
              if (ctlv.getTag() == tagValue) {
                  return ctlv;
              }
          }
          return null;
      }
  
      /**
       * Processes DISPLAY_TEXT proactive command from the SIM card.
       *
       * @param cmdDet Command Details container object.
       * @param ctlvs List of ComprehensionTlv objects following Command Details
       *        object and Device Identities object within the proactive command
       * @return true if the command is processing is pending and additional
       *         asynchronous processing is required.
       * @throws ResultException
       */
      private boolean processDisplayText(CommandDetails cmdDet,
              List<ComprehensionTlv> ctlvs)
              throws ResultException {
  
          CatLog.d(this, "process DisplayText");
  
          TextMessage textMsg = new TextMessage();
          IconId iconId = null;
  
          ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
                  ctlvs);
          if (ctlv != null) {
              textMsg.text = ValueParser.retrieveTextString(ctlv);
          }
          // If the tlv object doesn't exist or the it is a null object reply
          // with command not understood.
          if (textMsg.text == null) {
              throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
          }
  
          ctlv = searchForTag(ComprehensionTlvTag.IMMEDIATE_RESPONSE, ctlvs);
          if (ctlv != null) {
              textMsg.responseNeeded = false;
          }
          // parse icon identifier
          ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
          if (ctlv != null) {
              iconId = ValueParser.retrieveIconId(ctlv);
              textMsg.iconSelfExplanatory = iconId.selfExplanatory;
          }
          // parse tone duration
          ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
          if (ctlv != null) {
              textMsg.duration = ValueParser.retrieveDuration(ctlv);
          }
  
          // Parse command qualifier parameters.
          textMsg.isHighPriority = (cmdDet.commandQualifier & 0x01) != 0;
          textMsg.userClear = (cmdDet.commandQualifier & 0x80) != 0;
  
          mCmdParams = new DisplayTextParams(cmdDet, textMsg);
  
          if (iconId != null) {
              mloadIcon = true;
              mIconLoadState = LOAD_SINGLE_ICON;
              mIconLoader.loadIcon(iconId.recordNumber, this
                      .obtainMessage(MSG_ID_LOAD_ICON_DONE));
              return true;
          }
          return false;
      }
  
      /**
       * Processes SET_UP_IDLE_MODE_TEXT proactive command from the SIM card.
       *
       * @param cmdDet Command Details container object.
       * @param ctlvs List of ComprehensionTlv objects following Command Details
       *        object and Device Identities object within the proactive command
       * @return true if the command is processing is pending and additional
       *         asynchronous processing is required.
       * @throws ResultException
       */
      private boolean processSetUpIdleModeText(CommandDetails cmdDet,
              List<ComprehensionTlv> ctlvs) throws ResultException {
  
          CatLog.d(this, "process SetUpIdleModeText");
  
          TextMessage textMsg = new TextMessage();
          IconId iconId = null;
  
          ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
                  ctlvs);
          if (ctlv != null) {
              textMsg.text = ValueParser.retrieveTextString(ctlv);
          }
  
          ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
          if (ctlv != null) {
              iconId = ValueParser.retrieveIconId(ctlv);
              textMsg.iconSelfExplanatory = iconId.selfExplanatory;
          }
  
          /*
           * If the tlv object doesn't contain text and the icon is not self
           * explanatory then reply with command not understood.
           */
  
          if (textMsg.text == null && iconId != null && !textMsg.iconSelfExplanatory) {
              throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
          }
          mCmdParams = new DisplayTextParams(cmdDet, textMsg);
  
          if (iconId != null) {
              mloadIcon = true;
              mIconLoadState = LOAD_SINGLE_ICON;
              mIconLoader.loadIcon(iconId.recordNumber, this
                      .obtainMessage(MSG_ID_LOAD_ICON_DONE));
              return true;
          }
          return false;
      }
  
      /**
       * Processes GET_INKEY proactive command from the SIM card.
       *
       * @param cmdDet Command Details container object.
       * @param ctlvs List of ComprehensionTlv objects following Command Details
       *        object and Device Identities object within the proactive command
       * @return true if the command is processing is pending and additional
       *         asynchronous processing is required.
       * @throws ResultException
       */
      private boolean processGetInkey(CommandDetails cmdDet,
              List<ComprehensionTlv> ctlvs) throws ResultException {
  
          CatLog.d(this, "process GetInkey");
  
          Input input = new Input();
          IconId iconId = null;
  
          ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
                  ctlvs);
          if (ctlv != null) {
              input.text = ValueParser.retrieveTextString(ctlv);
          } else {
              throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
          }
          // parse icon identifier
          ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
          if (ctlv != null) {
              iconId = ValueParser.retrieveIconId(ctlv);
              input.iconSelfExplanatory = iconId.selfExplanatory;
          }
  
          // parse duration
          ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
          if (ctlv != null) {
              input.duration = ValueParser.retrieveDuration(ctlv);
          }
  
          input.minLen = 1;
          input.maxLen = 1;
  
          input.digitOnly = (cmdDet.commandQualifier & 0x01) == 0;
          input.ucs2 = (cmdDet.commandQualifier & 0x02) != 0;
          input.yesNo = (cmdDet.commandQualifier & 0x04) != 0;
          input.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
          input.echo = true;
  
          mCmdParams = new GetInputParams(cmdDet, input);
  
          if (iconId != null) {
              mloadIcon = true;
              mIconLoadState = LOAD_SINGLE_ICON;
              mIconLoader.loadIcon(iconId.recordNumber, this
                      .obtainMessage(MSG_ID_LOAD_ICON_DONE));
              return true;
          }
          return false;
      }
  
      /**
       * Processes GET_INPUT proactive command from the SIM card.
       *
       * @param cmdDet Command Details container object.
       * @param ctlvs List of ComprehensionTlv objects following Command Details
       *        object and Device Identities object within the proactive command
       * @return true if the command is processing is pending and additional
       *         asynchronous processing is required.
       * @throws ResultException
       */
      private boolean processGetInput(CommandDetails cmdDet,
              List<ComprehensionTlv> ctlvs) throws ResultException {
  
          CatLog.d(this, "process GetInput");
  
          Input input = new Input();
          IconId iconId = null;
  
          ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
                  ctlvs);
          if (ctlv != null) {
              input.text = ValueParser.retrieveTextString(ctlv);
          } else {
              throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
          }
  
          ctlv = searchForTag(ComprehensionTlvTag.RESPONSE_LENGTH, ctlvs);
          if (ctlv != null) {
              try {
                  byte[] rawValue = ctlv.getRawValue();
                  int valueIndex = ctlv.getValueIndex();
                  input.minLen = rawValue[valueIndex] & 0xff;
                  input.maxLen = rawValue[valueIndex + 1] & 0xff;
              } catch (IndexOutOfBoundsException e) {
                  throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
              }
          } else {
              throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
          }
  
          ctlv = searchForTag(ComprehensionTlvTag.DEFAULT_TEXT, ctlvs);
          if (ctlv != null) {
              input.defaultText = ValueParser.retrieveTextString(ctlv);
          }
          // parse icon identifier
          ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
          if (ctlv != null) {
              iconId = ValueParser.retrieveIconId(ctlv);
              input.iconSelfExplanatory = iconId.selfExplanatory;
          }
  
          ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
          if (ctlv != null) {
              input.duration = ValueParser.retrieveDuration(ctlv);
          }
  
          input.digitOnly = (cmdDet.commandQualifier & 0x01) == 0;
          input.ucs2 = (cmdDet.commandQualifier & 0x02) != 0;
          input.echo = (cmdDet.commandQualifier & 0x04) == 0;
          input.packed = (cmdDet.commandQualifier & 0x08) != 0;
          input.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
  
          // Truncate the maxLen if it exceeds the max number of chars that can
          // be encoded. Limit depends on DCS in Command Qualifier.
          if (input.ucs2 && input.maxLen > MAX_UCS2_CHARS) {
              CatLog.d(this, "UCS2: received maxLen = " + input.maxLen +
                    ", truncating to " + MAX_UCS2_CHARS);
              input.maxLen = MAX_UCS2_CHARS;
          } else if (!input.packed && input.maxLen > MAX_GSM7_DEFAULT_CHARS) {
              CatLog.d(this, "GSM 7Bit Default: received maxLen = " + input.maxLen +
                    ", truncating to " + MAX_GSM7_DEFAULT_CHARS);
              input.maxLen = MAX_GSM7_DEFAULT_CHARS;
          }
  
          mCmdParams = new GetInputParams(cmdDet, input);
  
          if (iconId != null) {
              mloadIcon = true;
              mIconLoadState = LOAD_SINGLE_ICON;
              mIconLoader.loadIcon(iconId.recordNumber, this
                      .obtainMessage(MSG_ID_LOAD_ICON_DONE));
              return true;
          }
          return false;
      }
  
      /**
       * Processes SELECT_ITEM proactive command from the SIM card.
       *
       * @param cmdDet Command Details container object.
       * @param ctlvs List of ComprehensionTlv objects following Command Details
       *        object and Device Identities object within the proactive command
       * @return true if the command is processing is pending and additional
       *         asynchronous processing is required.
       * @throws ResultException
       */
      private boolean processSelectItem(CommandDetails cmdDet,
              List<ComprehensionTlv> ctlvs) throws ResultException {
  
          CatLog.d(this, "process SelectItem");
  
          Menu menu = new Menu();
          IconId titleIconId = null;
          ItemsIconId itemsIconId = null;
          Iterator<ComprehensionTlv> iter = ctlvs.iterator();
  
          AppInterface.CommandType cmdType = AppInterface.CommandType
                  .fromInt(cmdDet.typeOfCommand);
  
          ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID,
                  ctlvs);
          if (ctlv != null) {
              menu.title = ValueParser.retrieveAlphaId(ctlv);
          } else if (cmdType == AppInterface.CommandType.SET_UP_MENU) {
              // According to spec ETSI TS 102 223 section 6.10.3, the
              // Alpha ID is mandatory (and also part of minimum set of
              // elements required) for SET_UP_MENU. If it is not received
              // by ME, then ME should respond with "error: missing minimum
              // information" and not "command performed successfully".
              throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
          }
  
          while (true) {
              ctlv = searchForNextTag(ComprehensionTlvTag.ITEM, iter);
              if (ctlv != null) {
                  menu.items.add(ValueParser.retrieveItem(ctlv));
              } else {
                  break;
              }
          }
  
          // We must have at least one menu item.
          if (menu.items.size() == 0) {
              throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
          }
  
          ctlv = searchForTag(ComprehensionTlvTag.ITEM_ID, ctlvs);
          if (ctlv != null) {
              // CAT items are listed 1...n while list start at 0, need to
              // subtract one.
              menu.defaultItem = ValueParser.retrieveItemId(ctlv) - 1;
          }
  
          ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
          if (ctlv != null) {
              mIconLoadState = LOAD_SINGLE_ICON;
              titleIconId = ValueParser.retrieveIconId(ctlv);
              menu.titleIconSelfExplanatory = titleIconId.selfExplanatory;
          }
  
          ctlv = searchForTag(ComprehensionTlvTag.ITEM_ICON_ID_LIST, ctlvs);
          if (ctlv != null) {
              mIconLoadState = LOAD_MULTI_ICONS;
              itemsIconId = ValueParser.retrieveItemsIconId(ctlv);
              menu.itemsIconSelfExplanatory = itemsIconId.selfExplanatory;
          }
  
          boolean presentTypeSpecified = (cmdDet.commandQualifier & 0x01) != 0;
          if (presentTypeSpecified) {
              if ((cmdDet.commandQualifier & 0x02) == 0) {
                  menu.presentationType = PresentationType.DATA_VALUES;
              } else {
                  menu.presentationType = PresentationType.NAVIGATION_OPTIONS;
              }
          }
          menu.softKeyPreferred = (cmdDet.commandQualifier & 0x04) != 0;
          menu.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
  
          mCmdParams = new SelectItemParams(cmdDet, menu, titleIconId != null);
  
          // Load icons data if needed.
          switch(mIconLoadState) {
          case LOAD_NO_ICON:
              return false;
          case LOAD_SINGLE_ICON:
              mloadIcon = true;
              mIconLoader.loadIcon(titleIconId.recordNumber, this
                      .obtainMessage(MSG_ID_LOAD_ICON_DONE));
              break;
          case LOAD_MULTI_ICONS:
              int[] recordNumbers = itemsIconId.recordNumbers;
              if (titleIconId != null) {
                  // Create a new array for all the icons (title and items).
                  recordNumbers = new int[itemsIconId.recordNumbers.length + 1];
                  recordNumbers[0] = titleIconId.recordNumber;
                  System.arraycopy(itemsIconId.recordNumbers, 0, recordNumbers,
                          1, itemsIconId.recordNumbers.length);
              }
              mloadIcon = true;
              mIconLoader.loadIcons(recordNumbers, this
                      .obtainMessage(MSG_ID_LOAD_ICON_DONE));
              break;
          }
          return true;
      }
  
      /**
       * Processes EVENT_NOTIFY message from baseband.
       *
       * @param cmdDet Command Details container object.
       * @param ctlvs List of ComprehensionTlv objects following Command Details
       *        object and Device Identities object within the proactive command
       * @return true if the command is processing is pending and additional
       *         asynchronous processing is required.
       */
      private boolean processEventNotify(CommandDetails cmdDet,
              List<ComprehensionTlv> ctlvs) throws ResultException {
  
          CatLog.d(this, "process EventNotify");
  
          TextMessage textMsg = new TextMessage();
          IconId iconId = null;
  
          ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID,
                  ctlvs);
          textMsg.text = ValueParser.retrieveAlphaId(ctlv);
  
          ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
          if (ctlv != null) {
              iconId = ValueParser.retrieveIconId(ctlv);
              textMsg.iconSelfExplanatory = iconId.selfExplanatory;
          }
  
          textMsg.responseNeeded = false;
          mCmdParams = new DisplayTextParams(cmdDet, textMsg);
  
          if (iconId != null) {
              mloadIcon = true;
              mIconLoadState = LOAD_SINGLE_ICON;
              mIconLoader.loadIcon(iconId.recordNumber, this
                      .obtainMessage(MSG_ID_LOAD_ICON_DONE));
              return true;
          }
          return false;
      }
  
      /**
       * Processes SET_UP_EVENT_LIST proactive command from the SIM card.
       *
       * @param cmdDet Command Details object retrieved.
       * @param ctlvs List of ComprehensionTlv objects following Command Details
       *        object and Device Identities object within the proactive command
       * @return false. This function always returns false meaning that the command
       *         processing is  not pending and additional asynchronous processing
       *         is not required.
       */
      private boolean processSetUpEventList(CommandDetails cmdDet,
              List<ComprehensionTlv> ctlvs) {
  
          CatLog.d(this, "process SetUpEventList");
          ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.EVENT_LIST, ctlvs);
          if (ctlv != null) {
              try {
                  byte[] rawValue = ctlv.getRawValue();
                  int valueIndex = ctlv.getValueIndex();
                  int valueLen = ctlv.getLength();
                  int[] eventList = new int[valueLen];
                  int eventValue = -1;
                  int i = 0;
                  while (valueLen > 0) {
                      eventValue = rawValue[valueIndex] & 0xff;
                      valueIndex++;
                      valueLen--;
  
                      switch (eventValue) {
                          case USER_ACTIVITY_EVENT:
                          case IDLE_SCREEN_AVAILABLE_EVENT:
                          case LANGUAGE_SELECTION_EVENT:
                          case BROWSER_TERMINATION_EVENT:
                          case BROWSING_STATUS_EVENT:
                              eventList[i] = eventValue;
                              i++;
                              break;
                          default:
                              break;
                      }
  
                  }
                  mCmdParams = new SetEventListParams(cmdDet, eventList);
              } catch (IndexOutOfBoundsException e) {
                  CatLog.e(this, " IndexOutofBoundException in processSetUpEventList");
              }
          }
          return false;
      }
  
      /**
       * Processes LAUNCH_BROWSER proactive command from the SIM card.
       *
       * @param cmdDet Command Details container object.
       * @param ctlvs List of ComprehensionTlv objects following Command Details
       *        object and Device Identities object within the proactive command
       * @return true if the command is processing is pending and additional
       *         asynchronous processing is required.
       * @throws ResultException
       */
      private boolean processLaunchBrowser(CommandDetails cmdDet,
              List<ComprehensionTlv> ctlvs) throws ResultException {
  
          CatLog.d(this, "process LaunchBrowser");
  
          TextMessage confirmMsg = new TextMessage();
          IconId iconId = null;
          String url = null;
  
          ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.URL, ctlvs);
          if (ctlv != null) {
              try {
                  byte[] rawValue = ctlv.getRawValue();
                  int valueIndex = ctlv.getValueIndex();
                  int valueLen = ctlv.getLength();
                  if (valueLen > 0) {
                      url = GsmAlphabet.gsm8BitUnpackedToString(rawValue,
                              valueIndex, valueLen);
                  } else {
                      url = null;
                  }
              } catch (IndexOutOfBoundsException e) {
                  throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
              }
          }
  
          // parse alpha identifier.
          ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
          confirmMsg.text = ValueParser.retrieveAlphaId(ctlv);
  
          // parse icon identifier
          ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
          if (ctlv != null) {
              iconId = ValueParser.retrieveIconId(ctlv);
              confirmMsg.iconSelfExplanatory = iconId.selfExplanatory;
          }
  
          // parse command qualifier value.
          LaunchBrowserMode mode;
          switch (cmdDet.commandQualifier) {
          case 0x00:
          default:
              mode = LaunchBrowserMode.LAUNCH_IF_NOT_ALREADY_LAUNCHED;
              break;
          case 0x02:
              mode = LaunchBrowserMode.USE_EXISTING_BROWSER;
              break;
          case 0x03:
              mode = LaunchBrowserMode.LAUNCH_NEW_BROWSER;
              break;
          }
  
          mCmdParams = new LaunchBrowserParams(cmdDet, confirmMsg, url, mode);
  
          if (iconId != null) {
              mIconLoadState = LOAD_SINGLE_ICON;
              mIconLoader.loadIcon(iconId.recordNumber, this
                      .obtainMessage(MSG_ID_LOAD_ICON_DONE));
              return true;
          }
          return false;
      }
  
       /**
       * Processes PLAY_TONE proactive command from the SIM card.
       *
       * @param cmdDet Command Details container object.
       * @param ctlvs List of ComprehensionTlv objects following Command Details
       *        object and Device Identities object within the proactive command
       * @return true if the command is processing is pending and additional
       *         asynchronous processing is required.t
       * @throws ResultException
       */
      private boolean processPlayTone(CommandDetails cmdDet,
              List<ComprehensionTlv> ctlvs) throws ResultException {
  
          CatLog.d(this, "process PlayTone");
  
          Tone tone = null;
          TextMessage textMsg = new TextMessage();
          Duration duration = null;
          IconId iconId = null;
  
          ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TONE, ctlvs);
          if (ctlv != null) {
              // Nothing to do for null objects.
              if (ctlv.getLength() > 0) {
                  try {
                      byte[] rawValue = ctlv.getRawValue();
                      int valueIndex = ctlv.getValueIndex();
                      int toneVal = rawValue[valueIndex];
                      tone = Tone.fromInt(toneVal);
                  } catch (IndexOutOfBoundsException e) {
                      throw new ResultException(
                              ResultCode.CMD_DATA_NOT_UNDERSTOOD);
                  }
              }
          }
          // parse alpha identifier
          ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
          if (ctlv != null) {
              textMsg.text = ValueParser.retrieveAlphaId(ctlv);
              // Assign the tone message text to empty string, if alpha identifier
              // data is null. If no alpha identifier tlv is present, then tone
              // message text will be null.
              if (textMsg.text == null) textMsg.text = "";
          }
          // parse tone duration
          ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
          if (ctlv != null) {
              duration = ValueParser.retrieveDuration(ctlv);
          }
          // parse icon identifier
          ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
          if (ctlv != null) {
              iconId = ValueParser.retrieveIconId(ctlv);
              textMsg.iconSelfExplanatory = iconId.selfExplanatory;
          }
  
          boolean vibrate = (cmdDet.commandQualifier & 0x01) != 0x00;
  
          textMsg.responseNeeded = false;
          mCmdParams = new PlayToneParams(cmdDet, textMsg, tone, duration, vibrate);
  
          if (iconId != null) {
              mIconLoadState = LOAD_SINGLE_ICON;
              mIconLoader.loadIcon(iconId.recordNumber, this
                      .obtainMessage(MSG_ID_LOAD_ICON_DONE));
              return true;
          }
          return false;
      }
  
      /**
       * Processes SETUP_CALL proactive command from the SIM card.
       *
       * @param cmdDet Command Details object retrieved from the proactive command
       *        object
       * @param ctlvs List of ComprehensionTlv objects following Command Details
       *        object and Device Identities object within the proactive command
       * @return true if the command is processing is pending and additional
       *         asynchronous processing is required.
       */
      private boolean processSetupCall(CommandDetails cmdDet,
              List<ComprehensionTlv> ctlvs) throws ResultException {
          CatLog.d(this, "process SetupCall");
  
          Iterator<ComprehensionTlv> iter = ctlvs.iterator();
          ComprehensionTlv ctlv = null;
          // User confirmation phase message.
          TextMessage confirmMsg = new TextMessage();
          // Call set up phase message.
          TextMessage callMsg = new TextMessage();
          IconId confirmIconId = null;
          IconId callIconId = null;
  
          // get confirmation message string.
          ctlv = searchForNextTag(ComprehensionTlvTag.ALPHA_ID, iter);
          confirmMsg.text = ValueParser.retrieveAlphaId(ctlv);
  
          ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
          if (ctlv != null) {
              confirmIconId = ValueParser.retrieveIconId(ctlv);
              confirmMsg.iconSelfExplanatory = confirmIconId.selfExplanatory;
          }
  
          // get call set up message string.
          ctlv = searchForNextTag(ComprehensionTlvTag.ALPHA_ID, iter);
          if (ctlv != null) {
              callMsg.text = ValueParser.retrieveAlphaId(ctlv);
          }
  
          ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
          if (ctlv != null) {
              callIconId = ValueParser.retrieveIconId(ctlv);
              callMsg.iconSelfExplanatory = callIconId.selfExplanatory;
          }
  
          mCmdParams = new CallSetupParams(cmdDet, confirmMsg, callMsg);
  
          if (confirmIconId != null || callIconId != null) {
              mIconLoadState = LOAD_MULTI_ICONS;
              int[] recordNumbers = new int[2];
              recordNumbers[0] = confirmIconId != null
                      ? confirmIconId.recordNumber : -1;
              recordNumbers[1] = callIconId != null ? callIconId.recordNumber
                      : -1;
  
              mIconLoader.loadIcons(recordNumbers, this
                      .obtainMessage(MSG_ID_LOAD_ICON_DONE));
              return true;
          }
          return false;
      }
  
      private boolean processProvideLocalInfo(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)
              throws ResultException {
          CatLog.d(this, "process ProvideLocalInfo");
          switch (cmdDet.commandQualifier) {
              case DTTZ_SETTING:
                  CatLog.d(this, "PLI [DTTZ_SETTING]");
                  mCmdParams = new CommandParams(cmdDet);
                  break;
              case LANGUAGE_SETTING:
                  CatLog.d(this, "PLI [LANGUAGE_SETTING]");
                  mCmdParams = new CommandParams(cmdDet);
                  break;
              default:
                  CatLog.d(this, "PLI[" + cmdDet.commandQualifier + "] Command Not Supported");
                  mCmdParams = new CommandParams(cmdDet);
                  throw new ResultException(ResultCode.BEYOND_TERMINAL_CAPABILITY);
          }
          return false;
      }
  
      /**
       * Processes LANGUAGE_NOTIFICATION proactive command from the SIM card.
       *
       * The SPECIFIC_LANGUAGE notification sets the specified language.
       * The NON_SPECIFIC_LANGUAGE notification restores the last specifically set language.
       *
       * @param cmdDet Command Details object retrieved from the proactive command object
       * @param ctlvs List of ComprehensionTlv objects following Command Details
       *        object and Device Identities object within the proactive command
       * @return false. This function always returns false meaning that the command
       *         processing is  not pending and additional asynchronous processing
       *         is not required.
       */
      private boolean processLanguageNotification(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)
              throws ResultException {
          CatLog.d(this, "process Language Notification");
  
          String desiredLanguage = null;
          String currentLanguage = Locale.getDefault().getLanguage();
          switch (cmdDet.commandQualifier) {
              case NON_SPECIFIC_LANGUAGE:
                  if (!TextUtils.isEmpty(mSavedLanguage) && (!TextUtils.isEmpty(mRequestedLanguage)
                          && mRequestedLanguage.equals(currentLanguage))) {
                      CatLog.d(this, "Non-specific language notification changes the language "
                              + "setting back to " + mSavedLanguage);
                      desiredLanguage = mSavedLanguage;
                  }
  
                  mSavedLanguage = null;
                  mRequestedLanguage = null;
                  break;
              case SPECIFIC_LANGUAGE:
                  ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.LANGUAGE, ctlvs);
                  if (ctlv != null) {
                      int valueLen = ctlv.getLength();
                      if (valueLen != 2) {
                          throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
                      }
  
                      byte[] rawValue = ctlv.getRawValue();
                      int valueIndex = ctlv.getValueIndex();
                      desiredLanguage = GsmAlphabet.gsm8BitUnpackedToString(rawValue, valueIndex, 2);
  
                      if (TextUtils.isEmpty(mSavedLanguage) || (!TextUtils.isEmpty(mRequestedLanguage)
                              && !mRequestedLanguage.equals(currentLanguage))) {
                          mSavedLanguage = currentLanguage;
                      }
                      mRequestedLanguage = desiredLanguage;
                      CatLog.d(this, "Specific language notification changes the language setting to "
                              + mRequestedLanguage);
                  }
                  break;
              default:
                  CatLog.d(this, "LN[" + cmdDet.commandQualifier + "] Command Not Supported");
                  break;
          }
  
          mCmdParams = new LanguageParams(cmdDet, desiredLanguage);
          return false;
      }
  
      private boolean processBIPClient(CommandDetails cmdDet,
                                       List<ComprehensionTlv> ctlvs) throws ResultException {
          AppInterface.CommandType commandType =
                                      AppInterface.CommandType.fromInt(cmdDet.typeOfCommand);
          if (commandType != null) {
              CatLog.d(this, "process "+ commandType.name());
          }
  
          TextMessage textMsg = new TextMessage();
          IconId iconId = null;
          ComprehensionTlv ctlv = null;
          boolean has_alpha_id = false;
  
          // parse alpha identifier
          ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
          if (ctlv != null) {
              textMsg.text = ValueParser.retrieveAlphaId(ctlv);
              CatLog.d(this, "alpha TLV text=" + textMsg.text);
              has_alpha_id = true;
          }
  
          // parse icon identifier
          ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
          if (ctlv != null) {
              iconId = ValueParser.retrieveIconId(ctlv);
              textMsg.iconSelfExplanatory = iconId.selfExplanatory;
          }
  
          textMsg.responseNeeded = false;
          mCmdParams = new BIPClientParams(cmdDet, textMsg, has_alpha_id);
  
          if (iconId != null) {
              mIconLoadState = LOAD_SINGLE_ICON;
              mIconLoader.loadIcon(iconId.recordNumber, obtainMessage(MSG_ID_LOAD_ICON_DONE));
              return true;
          }
          return false;
      }
  
      @UnsupportedAppUsage
      public void dispose() {
          mIconLoader.dispose();
          mIconLoader = null;
          mCmdParams = null;
          mCaller = null;
          sInstance = null;
      }
  }
  