docs: public static Message parseMessage(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
          ParserUtils.assertAtStartTag(parser);
          assert (parser.getName().equals(Message.ELEMENT));
  
          XmlEnvironment messageXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
          final int initialDepth = parser.getDepth();
          Message message = new Message();
          message.setStanzaId(parser.getAttributeValue("", "id"));
          message.setTo(ParserUtils.getJidAttribute(parser, "to"));
          message.setFrom(ParserUtils.getJidAttribute(parser, "from"));
          String typeString = parser.getAttributeValue("", "type");
          if (typeString != null) {
              message.setType(Message.Type.fromString(typeString));
          }
          String language = ParserUtils.getXmlLang(parser);
          message.setLanguage(language);
  
          // Parse sub-elements. We include extra logic to make sure the values
          // are only read once. This is because it's possible for the names to appear
          // in arbitrary sub-elements.
          String thread = null;
          outerloop: while (true) {
              int eventType = parser.next();
              switch (eventType) {
              case XmlPullParser.START_TAG:
                  String elementName = parser.getName();
                  String namespace = parser.getNamespace();
                  switch (elementName) {
                  case "subject":
                      String xmlLangSubject = ParserUtils.getXmlLang(parser);
                      String subject = parseElementText(parser);
  
                      if (message.getSubject(xmlLangSubject) == null) {
                          message.addSubject(xmlLangSubject, subject);
                      }
                      break;
                  case "thread":
                      if (thread == null) {
                          thread = parser.nextText();
                      }
                      break;
                  case "error":
                      message.setError(parseError(parser, messageXmlEnvironment));
                      break;
                   default:
                      PacketParserUtils.addExtensionElement(message, parser, elementName, namespace, messageXmlEnvironment);
                      break;
                  }
                  break;
              case XmlPullParser.END_TAG:
                  if (parser.getDepth() == initialDepth) {
                      break outerloop;
                  }
                  break;
              }
          }
  
          message.setThread(thread);
  
          // TODO check for duplicate body elements. This means we need to check for duplicate xml:lang pairs and for
          // situations where we have a body element with an explicit xml lang set and once where the value is inherited
          // and both values are equal.
  
          return message;
      }