/**
   * Get an SMS-SUBMIT PDU for a data message to a destination address & port
   *
   * @param scAddress Service Centre address. null == use default
   * @param destinationAddress the address of the destination for the message
   * @param destinationPort the port to deliver the message to at the destination
   * @param data the data for the message
   * @return a <code>SubmitPdu</code> containing the encoded SC address, if applicable, and the
   *     encoded message. Returns null on encode error.
   */
  public static SubmitPdu getSubmitPdu(
      String scAddress,
      String destinationAddress,
      int destinationPort,
      byte[] data,
      boolean statusReportRequested) {

    SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
    portAddrs.destPort = destinationPort;
    portAddrs.origPort = 0;
    portAddrs.areEightBits = false;

    SmsHeader smsHeader = new SmsHeader();
    smsHeader.portAddrs = portAddrs;

    byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader);

    if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) {
      Rlog.e(
          LOG_TAG,
          "SMS data message may only contain "
              + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1)
              + " bytes");
      return null;
    }

    SubmitPdu ret = new SubmitPdu();
    ByteArrayOutputStream bo =
        getSubmitPduHead(
            scAddress,
            destinationAddress,
            (byte) 0x41, // MTI = SMS-SUBMIT,
            // TP-UDHI = true
            statusReportRequested,
            ret);

    // TP-Data-Coding-Scheme
    // No class, 8 bit data
    bo.write(0x04);

    // (no TP-Validity-Period)

    // Total size
    bo.write(data.length + smsHeaderData.length + 1);

    // User data header
    bo.write(smsHeaderData.length);
    bo.write(smsHeaderData, 0, smsHeaderData.length);

    // User data
    bo.write(data, 0, data.length);

    ret.encodedMessage = bo.toByteArray();
    return ret;
  }
    /**
     * Pulls the user data out of the PDU, and separates the payload from the header if there is
     * one.
     *
     * @param hasUserDataHeader true if there is a user data header
     * @param dataInSeptets true if the data payload is in septets instead of octets
     * @return the number of septets or octets in the user data payload
     */
    int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
      int offset = mCur;
      int userDataLength = mPdu[offset++] & 0xff;
      int headerSeptets = 0;
      int userDataHeaderLength = 0;

      if (hasUserDataHeader) {
        userDataHeaderLength = mPdu[offset++] & 0xff;

        byte[] udh = new byte[userDataHeaderLength];
        System.arraycopy(mPdu, offset, udh, 0, userDataHeaderLength);
        mUserDataHeader = SmsHeader.fromByteArray(udh);
        offset += userDataHeaderLength;

        int headerBits = (userDataHeaderLength + 1) * 8;
        headerSeptets = headerBits / 7;
        headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
        mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
      }

      int bufferLen;
      if (dataInSeptets) {
        /*
         * Here we just create the user data length to be the remainder of
         * the pdu minus the user data header, since userDataLength means
         * the number of uncompressed septets.
         */
        bufferLen = mPdu.length - offset;
      } else {
        /*
         * userDataLength is the count of octets, so just subtract the
         * user data header.
         */
        bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0);
        if (bufferLen < 0) {
          bufferLen = 0;
        }
      }

      mUserData = new byte[bufferLen];
      System.arraycopy(mPdu, offset, mUserData, 0, mUserData.length);
      mCur = offset;

      if (dataInSeptets) {
        // Return the number of septets
        int count = userDataLength - headerSeptets;
        // If count < 0, return 0 (means UDL was probably incorrect)
        return count < 0 ? 0 : count;
      } else {
        // Return the number of octets
        return mUserData.length;
      }
    }
 /** {@inheritDoc} */
 @Override
 protected SmsTracker getNewSubmitPduTracker(
     String destinationAddress,
     String scAddress,
     String message,
     SmsHeader smsHeader,
     int encoding,
     PendingIntent sentIntent,
     PendingIntent deliveryIntent,
     boolean lastPart,
     int priority,
     boolean isExpectMore,
     int validityPeriod,
     AtomicInteger unsentPartCount,
     AtomicBoolean anyPartFailed,
     Uri messageUri,
     String fullMessageText) {
   SmsMessage.SubmitPdu pdu =
       SmsMessage.getSubmitPdu(
           scAddress,
           destinationAddress,
           message,
           deliveryIntent != null,
           SmsHeader.toByteArray(smsHeader),
           encoding,
           smsHeader.languageTable,
           smsHeader.languageShiftTable,
           validityPeriod);
   if (pdu != null) {
     HashMap map = getSmsTrackerMap(destinationAddress, scAddress, message, pdu);
     return getSmsTracker(
         map,
         sentIntent,
         deliveryIntent,
         getFormat(),
         unsentPartCount,
         anyPartFailed,
         messageUri,
         smsHeader,
         (!lastPart || isExpectMore),
         fullMessageText,
         true /*isText*/,
         validityPeriod);
   } else {
     Rlog.e(TAG, "GsmSMSDispatcher.sendNewSubmitPdu(): getSubmitPdu() returned null");
     return null;
   }
 }
  /**
   * Send a multi-part text based SMS which already passed SMS control check.
   *
   * It is the working function for sendMultipartText().
   *
   * @param destinationAddress the address to send the message to
   * @param scAddress is the service center address or null to use
   *   the current default SMSC
   * @param parts an <code>ArrayList</code> of strings that, in order,
   *   comprise the original message
   * @param sentIntents if not null, an <code>ArrayList</code> of
   *   <code>PendingIntent</code>s (one for each message part) that is
   *   broadcast when the corresponding message part has been sent.
   *   The result code will be <code>Activity.RESULT_OK<code> for success,
   *   or one of these errors:
   *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
   *   <code>RESULT_ERROR_RADIO_OFF</code>
   *   <code>RESULT_ERROR_NULL_PDU</code>.
   * @param deliveryIntents if not null, an <code>ArrayList</code> of
   *   <code>PendingIntent</code>s (one for each message part) that is
   *   broadcast when the corresponding message part has been delivered
   *   to the recipient.  The raw pdu of the status report is in the
   *   extended data ("pdu").
   */
  private void sendMultipartTextWithPermit(
      String destinationAddress,
      String scAddress,
      ArrayList<String> parts,
      ArrayList<PendingIntent> sentIntents,
      ArrayList<PendingIntent> deliveryIntents) {

    // check if in service
    int ss = mPhone.getServiceState().getState();
    if (ss != ServiceState.STATE_IN_SERVICE) {
      for (int i = 0, count = parts.size(); i < count; i++) {
        PendingIntent sentIntent = null;
        if (sentIntents != null && sentIntents.size() > i) {
          sentIntent = sentIntents.get(i);
        }
        SmsTracker tracker = SmsTrackerFactory(null, sentIntent, null);
        handleNotInService(ss, tracker);
      }
      return;
    }

    int refNumber = getNextConcatenatedRef() & 0x00FF;
    int msgCount = parts.size();
    int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN;

    mRemainingMessages = msgCount;

    TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
    for (int i = 0; i < msgCount; i++) {
      TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
      if (encoding != details.codeUnitSize
          && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN
              || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
        encoding = details.codeUnitSize;
      }
      encodingForParts[i] = details;
    }

    for (int i = 0; i < msgCount; i++) {
      SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
      concatRef.refNumber = refNumber;
      concatRef.seqNumber = i + 1; // 1-based sequence
      concatRef.msgCount = msgCount;
      concatRef.isEightBits = false;
      SmsHeader smsHeader = new SmsHeader();
      smsHeader.concatRef = concatRef;
      if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
        smsHeader.languageTable = encodingForParts[i].languageTable;
        smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
      }

      PendingIntent sentIntent = null;
      if (sentIntents != null && sentIntents.size() > i) {
        sentIntent = sentIntents.get(i);
      }

      PendingIntent deliveryIntent = null;
      if (deliveryIntents != null && deliveryIntents.size() > i) {
        deliveryIntent = deliveryIntents.get(i);
      }

      SmsMessage.SubmitPdu pdus =
          SmsMessage.getSubmitPdu(
              scAddress,
              destinationAddress,
              parts.get(i),
              deliveryIntent != null,
              SmsHeader.toByteArray(smsHeader),
              encoding,
              smsHeader.languageTable,
              smsHeader.languageShiftTable);

      HashMap<String, Object> map = new HashMap<String, Object>();
      map.put("smsc", pdus.encodedScAddress);
      map.put("pdu", pdus.encodedMessage);

      SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent);
      sendSms(tracker);
    }
  }
  /** {@inheritDoc} */
  protected void sendMultipartText(
      String destinationAddress,
      String scAddress,
      ArrayList<String> parts,
      ArrayList<PendingIntent> sentIntents,
      ArrayList<PendingIntent> deliveryIntents) {

    int refNumber = getNextConcatenatedRef() & 0x00FF;
    int msgCount = parts.size();
    int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN;

    mRemainingMessages = msgCount;

    TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
    for (int i = 0; i < msgCount; i++) {
      TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
      if (encoding != details.codeUnitSize
          && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN
              || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
        encoding = details.codeUnitSize;
      }
      encodingForParts[i] = details;
    }

    for (int i = 0; i < msgCount; i++) {
      SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
      concatRef.refNumber = refNumber;
      concatRef.seqNumber = i + 1; // 1-based sequence
      concatRef.msgCount = msgCount;
      // TODO: We currently set this to true since our messaging app will never
      // send more than 255 parts (it converts the message to MMS well before that).
      // However, we should support 3rd party messaging apps that might need 16-bit
      // references
      // Note:  It's not sufficient to just flip this bit to true; it will have
      // ripple effects (several calculations assume 8-bit ref).
      concatRef.isEightBits = true;
      SmsHeader smsHeader = new SmsHeader();
      smsHeader.concatRef = concatRef;
      if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
        smsHeader.languageTable = encodingForParts[i].languageTable;
        smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
      }

      PendingIntent sentIntent = null;
      if (sentIntents != null && sentIntents.size() > i) {
        sentIntent = sentIntents.get(i);
      }

      PendingIntent deliveryIntent = null;
      if (deliveryIntents != null && deliveryIntents.size() > i) {
        deliveryIntent = deliveryIntents.get(i);
      }

      SmsMessage.SubmitPdu pdus =
          SmsMessage.getSubmitPdu(
              scAddress,
              destinationAddress,
              parts.get(i),
              deliveryIntent != null,
              SmsHeader.toByteArray(smsHeader),
              encoding,
              smsHeader.languageTable,
              smsHeader.languageShiftTable);

      sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent);
    }
  }
  /**
   * Get an SMS-SUBMIT PDU for a destination address and a message using the specified encoding.
   *
   * @param scAddress Service Centre address. Null means use default.
   * @param encoding Encoding defined by constants in
   *     com.android.internal.telephony.SmsConstants.ENCODING_*
   * @param languageTable
   * @param languageShiftTable
   * @return a <code>SubmitPdu</code> containing the encoded SC address, if applicable, and the
   *     encoded message. Returns null on encode error.
   * @hide
   */
  public static SubmitPdu getSubmitPdu(
      String scAddress,
      String destinationAddress,
      String message,
      boolean statusReportRequested,
      byte[] header,
      int encoding,
      int languageTable,
      int languageShiftTable) {

    // Perform null parameter checks.
    if (message == null || destinationAddress == null) {
      return null;
    }

    if (encoding == ENCODING_UNKNOWN) {
      // Find the best encoding to use
      TextEncodingDetails ted = calculateLength(message, false);
      encoding = ted.codeUnitSize;
      languageTable = ted.languageTable;
      languageShiftTable = ted.languageShiftTable;

      if (encoding == ENCODING_7BIT && (languageTable != 0 || languageShiftTable != 0)) {
        if (header != null) {
          SmsHeader smsHeader = SmsHeader.fromByteArray(header);
          if (smsHeader.languageTable != languageTable
              || smsHeader.languageShiftTable != languageShiftTable) {
            Rlog.w(
                LOG_TAG,
                "Updating language table in SMS header: "
                    + smsHeader.languageTable
                    + " -> "
                    + languageTable
                    + ", "
                    + smsHeader.languageShiftTable
                    + " -> "
                    + languageShiftTable);
            smsHeader.languageTable = languageTable;
            smsHeader.languageShiftTable = languageShiftTable;
            header = SmsHeader.toByteArray(smsHeader);
          }
        } else {
          SmsHeader smsHeader = new SmsHeader();
          smsHeader.languageTable = languageTable;
          smsHeader.languageShiftTable = languageShiftTable;
          header = SmsHeader.toByteArray(smsHeader);
        }
      }
    }

    SubmitPdu ret = new SubmitPdu();
    // MTI = SMS-SUBMIT, UDHI = header != null
    byte mtiByte = (byte) (0x01 | (header != null ? 0x40 : 0x00));
    ByteArrayOutputStream bo =
        getSubmitPduHead(scAddress, destinationAddress, mtiByte, statusReportRequested, ret);

    // User Data (and length)
    byte[] userData;
    try {
      if (encoding == ENCODING_7BIT) {
        userData =
            GsmAlphabet.stringToGsm7BitPackedWithHeader(
                message, header, languageTable, languageShiftTable);
      } else { // assume UCS-2
        try {
          userData = encodeUCS2(message, header);
        } catch (UnsupportedEncodingException uex) {
          Rlog.e(LOG_TAG, "Implausible UnsupportedEncodingException ", uex);
          return null;
        }
      }
    } catch (EncodeException ex) {
      // Encoding to the 7-bit alphabet failed. Let's see if we can
      // send it as a UCS-2 encoded message
      try {
        userData = encodeUCS2(message, header);
        encoding = ENCODING_16BIT;
      } catch (UnsupportedEncodingException uex) {
        Rlog.e(LOG_TAG, "Implausible UnsupportedEncodingException ", uex);
        return null;
      }
    }

    if (encoding == ENCODING_7BIT) {
      if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
        // Message too long
        Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)");
        return null;
      }
      // TP-Data-Coding-Scheme
      // Default encoding, uncompressed
      // To test writing messages to the SIM card, change this value 0x00
      // to 0x12, which means "bits 1 and 0 contain message class, and the
      // class is 2". Note that this takes effect for the sender. In other
      // words, messages sent by the phone with this change will end up on
      // the receiver's SIM card. You can then send messages to yourself
      // (on a phone with this change) and they'll end up on the SIM card.
      bo.write(0x00);
    } else { // assume UCS-2
      if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
        // Message too long
        Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)");
        return null;
      }
      // TP-Data-Coding-Scheme
      // UCS-2 encoding, uncompressed
      bo.write(0x08);
    }

    // (no TP-Validity-Period)
    bo.write(userData, 0, userData.length);
    ret.encodedMessage = bo.toByteArray();
    return ret;
  }