1 /* 2 * Copyright (C) 2013 Samsung System LSI 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 package com.android.bluetooth.map; 16 17 import android.database.Cursor; 18 import android.util.Base64; 19 import android.util.Log; 20 21 import com.android.bluetooth.mapapi.BluetoothMapContract; 22 23 import java.io.ByteArrayOutputStream; 24 import java.io.UnsupportedEncodingException; 25 import java.nio.charset.Charset; 26 import java.nio.charset.IllegalCharsetNameException; 27 import java.text.SimpleDateFormat; 28 import java.util.Arrays; 29 import java.util.BitSet; 30 import java.util.Calendar; 31 import java.util.regex.Matcher; 32 import java.util.regex.Pattern; 33 34 35 /** 36 * Various utility methods and generic defines that can be used throughout MAPS 37 */ 38 public class BluetoothMapUtils { 39 40 private static final String TAG = "BluetoothMapUtils"; 41 private static final boolean D = BluetoothMapService.DEBUG; 42 private static final boolean V = BluetoothMapService.VERBOSE; 43 /* We use the upper 4 bits for the type mask. 44 * TODO: When more types are needed, consider just using a number 45 * in stead of a bit to indicate the message type. Then 4 46 * bit can be use for 16 different message types. 47 */ 48 private static final long HANDLE_TYPE_MASK = (((long) 0xff) << 56); 49 private static final long HANDLE_TYPE_MMS_MASK = (((long) 0x01) << 56); 50 private static final long HANDLE_TYPE_EMAIL_MASK = (((long) 0x02) << 56); 51 private static final long HANDLE_TYPE_SMS_GSM_MASK = (((long) 0x04) << 56); 52 private static final long HANDLE_TYPE_SMS_CDMA_MASK = (((long) 0x08) << 56); 53 private static final long HANDLE_TYPE_IM_MASK = (((long) 0x10) << 56); 54 55 public static final long CONVO_ID_TYPE_SMS_MMS = 1; 56 public static final long CONVO_ID_TYPE_EMAIL_IM = 2; 57 58 // MAP supported feature bit - included from MAP Spec 1.2 59 static final int MAP_FEATURE_DEFAULT_BITMASK = 0x0000001F; 60 61 static final int MAP_FEATURE_NOTIFICATION_REGISTRATION_BIT = 1 << 0; 62 static final int MAP_FEATURE_NOTIFICATION_BIT = 1 << 1; 63 static final int MAP_FEATURE_BROWSING_BIT = 1 << 2; 64 static final int MAP_FEATURE_UPLOADING_BIT = 1 << 3; 65 static final int MAP_FEATURE_DELETE_BIT = 1 << 4; 66 static final int MAP_FEATURE_INSTANCE_INFORMATION_BIT = 1 << 5; 67 static final int MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT = 1 << 6; 68 static final int MAP_FEATURE_EVENT_REPORT_V12_BIT = 1 << 7; 69 static final int MAP_FEATURE_MESSAGE_FORMAT_V11_BIT = 1 << 8; 70 static final int MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT = 1 << 9; 71 static final int MAP_FEATURE_PERSISTENT_MESSAGE_HANDLE_BIT = 1 << 10; 72 static final int MAP_FEATURE_DATABASE_INDENTIFIER_BIT = 1 << 11; 73 static final int MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT = 1 << 12; 74 static final int MAP_FEATURE_CONVERSATION_VERSION_COUNTER_BIT = 1 << 13; 75 static final int MAP_FEATURE_PARTICIPANT_PRESENCE_CHANGE_BIT = 1 << 14; 76 static final int MAP_FEATURE_PARTICIPANT_CHAT_STATE_CHANGE_BIT = 1 << 15; 77 78 static final int MAP_FEATURE_PBAP_CONTACT_CROSS_REFERENCE_BIT = 1 << 16; 79 static final int MAP_FEATURE_NOTIFICATION_FILTERING_BIT = 1 << 17; 80 static final int MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT = 1 << 18; 81 82 static final String MAP_V10_STR = "1.0"; 83 static final String MAP_V11_STR = "1.1"; 84 static final String MAP_V12_STR = "1.2"; 85 86 // Event Report versions 87 static final int MAP_EVENT_REPORT_V10 = 10; // MAP spec 1.1 88 static final int MAP_EVENT_REPORT_V11 = 11; // MAP spec 1.2 89 static final int MAP_EVENT_REPORT_V12 = 12; // MAP spec 1.3 'to be' incl. IM 90 91 // Message Format versions 92 static final int MAP_MESSAGE_FORMAT_V10 = 10; // MAP spec below 1.3 93 static final int MAP_MESSAGE_FORMAT_V11 = 11; // MAP spec 1.3 94 95 // Message Listing Format versions 96 static final int MAP_MESSAGE_LISTING_FORMAT_V10 = 10; // MAP spec below 1.3 97 static final int MAP_MESSAGE_LISTING_FORMAT_V11 = 11; // MAP spec 1.3 98 99 private static boolean mPeerSupportUtcTimeStamp = false; 100 101 /** 102 * This enum is used to convert from the bMessage type property to a type safe 103 * type. Hence do not change the names of the enum values. 104 */ 105 public enum TYPE { 106 NONE, EMAIL, SMS_GSM, SMS_CDMA, MMS, IM; 107 private static TYPE[] sAllValues = values(); 108 fromOrdinal(int n)109 public static TYPE fromOrdinal(int n) { 110 if (n < sAllValues.length) { 111 return sAllValues[n]; 112 } 113 return NONE; 114 } 115 } 116 printCursor(Cursor c)117 public static void printCursor(Cursor c) { 118 if (D) { 119 StringBuilder sb = new StringBuilder(); 120 sb.append("\nprintCursor:\n"); 121 for (int i = 0; i < c.getColumnCount(); i++) { 122 if (c.getColumnName(i).equals(BluetoothMapContract.MessageColumns.DATE) 123 || c.getColumnName(i) 124 .equals(BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY) 125 || c.getColumnName(i) 126 .equals(BluetoothMapContract.ChatStatusColumns.LAST_ACTIVE) 127 || c.getColumnName(i) 128 .equals(BluetoothMapContract.PresenceColumns.LAST_ONLINE)) { 129 sb.append(" ") 130 .append(c.getColumnName(i)) 131 .append(" : ") 132 .append(getDateTimeString(c.getLong(i))) 133 .append("\n"); 134 } else { 135 sb.append(" ") 136 .append(c.getColumnName(i)) 137 .append(" : ") 138 .append(c.getString(i)) 139 .append("\n"); 140 } 141 } 142 Log.d(TAG, sb.toString()); 143 } 144 } 145 getLongAsString(long v)146 public static String getLongAsString(long v) { 147 char[] result = new char[16]; 148 int v1 = (int) (v & 0xffffffff); 149 int v2 = (int) ((v >> 32) & 0xffffffff); 150 int c; 151 for (int i = 0; i < 8; i++) { 152 c = v2 & 0x0f; 153 c += (c < 10) ? '0' : ('A' - 10); 154 result[7 - i] = (char) c; 155 v2 >>= 4; 156 c = v1 & 0x0f; 157 c += (c < 10) ? '0' : ('A' - 10); 158 result[15 - i] = (char) c; 159 v1 >>= 4; 160 } 161 return new String(result); 162 } 163 164 /** 165 * Converts a hex-string to a long - please mind that Java has no unsigned data types, hence 166 * any value passed to this function, which has the upper bit set, will return a negative value. 167 * The bitwise content of the variable will however be the same. 168 * Will ignore any white-space characters as well as '-' seperators 169 * @param valueStr a hexstring - NOTE: shall not contain any "0x" prefix. 170 * @return 171 * @throws UnsupportedEncodingException if "US-ASCII" charset is not supported, 172 * NullPointerException if a null pointer is passed to the function, 173 * NumberFormatException if the string contains invalid characters. 174 * 175 */ getLongFromString(String valueStr)176 public static long getLongFromString(String valueStr) throws UnsupportedEncodingException { 177 if (valueStr == null) { 178 throw new NullPointerException(); 179 } 180 if (V) { 181 Log.i(TAG, "getLongFromString(): converting: " + valueStr); 182 } 183 byte[] nibbles; 184 nibbles = valueStr.getBytes("US-ASCII"); 185 if (V) { 186 Log.i(TAG, " byte values: " + Arrays.toString(nibbles)); 187 } 188 byte c; 189 int count = 0; 190 int length = nibbles.length; 191 long value = 0; 192 for (int i = 0; i != length; i++) { 193 c = nibbles[i]; 194 if (c >= '0' && c <= '9') { 195 c -= '0'; 196 } else if (c >= 'A' && c <= 'F') { 197 c -= ('A' - 10); 198 } else if (c >= 'a' && c <= 'f') { 199 c -= ('a' - 10); 200 } else if (c <= ' ' || c == '-') { 201 if (V) { 202 Log.v(TAG, 203 "Skipping c = '" + new String(new byte[]{(byte) c}, "US-ASCII") + "'"); 204 } 205 continue; // Skip any whitespace and '-' (which is used for UUIDs) 206 } else { 207 throw new NumberFormatException("Invalid character:" + c); 208 } 209 value = value << 4; // The last nibble shall not be shifted 210 value += c; 211 count++; 212 if (count > 16) { 213 throw new NullPointerException("String to large - count: " + count); 214 } 215 } 216 if (V) { 217 Log.i(TAG, " length: " + count); 218 } 219 return value; 220 } 221 222 private static final int LONG_LONG_LENGTH = 32; 223 getLongLongAsString(long vLow, long vHigh)224 public static String getLongLongAsString(long vLow, long vHigh) { 225 char[] result = new char[LONG_LONG_LENGTH]; 226 int v1 = (int) (vLow & 0xffffffff); 227 int v2 = (int) ((vLow >> 32) & 0xffffffff); 228 int v3 = (int) (vHigh & 0xffffffff); 229 int v4 = (int) ((vHigh >> 32) & 0xffffffff); 230 int c, d, i; 231 // Handle the lower bytes 232 for (i = 0; i < 8; i++) { 233 c = v2 & 0x0f; 234 c += (c < 10) ? '0' : ('A' - 10); 235 d = v4 & 0x0f; 236 d += (d < 10) ? '0' : ('A' - 10); 237 result[23 - i] = (char) c; 238 result[7 - i] = (char) d; 239 v2 >>= 4; 240 v4 >>= 4; 241 c = v1 & 0x0f; 242 c += (c < 10) ? '0' : ('A' - 10); 243 d = v3 & 0x0f; 244 d += (d < 10) ? '0' : ('A' - 10); 245 result[31 - i] = (char) c; 246 result[15 - i] = (char) d; 247 v1 >>= 4; 248 v3 >>= 4; 249 } 250 // Remove any leading 0's 251 for (i = 0; i < LONG_LONG_LENGTH; i++) { 252 if (result[i] != '0') { 253 break; 254 } 255 } 256 return new String(result, i, LONG_LONG_LENGTH - i); 257 } 258 259 260 /** 261 * Convert a Content Provider handle and a Messagetype into a unique handle 262 * @param cpHandle content provider handle 263 * @param messageType message type (TYPE_MMS/TYPE_SMS_GSM/TYPE_SMS_CDMA/TYPE_EMAIL) 264 * @return String Formatted Map Handle 265 */ getMapHandle(long cpHandle, TYPE messageType)266 public static String getMapHandle(long cpHandle, TYPE messageType) { 267 String mapHandle = "-1"; 268 /* Avoid NPE for possible "null" value of messageType */ 269 if (messageType != null) { 270 switch (messageType) { 271 case MMS: 272 mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_MMS_MASK); 273 break; 274 case SMS_GSM: 275 mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_SMS_GSM_MASK); 276 break; 277 case SMS_CDMA: 278 mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_SMS_CDMA_MASK); 279 break; 280 case EMAIL: 281 mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_EMAIL_MASK); 282 break; 283 case IM: 284 mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_IM_MASK); 285 break; 286 case NONE: 287 break; 288 default: 289 throw new IllegalArgumentException("Message type not supported"); 290 } 291 } else { 292 if (D) { 293 Log.e(TAG, " Invalid messageType input"); 294 } 295 } 296 return mapHandle; 297 298 } 299 300 /** 301 * Convert a Content Provider handle and a Messagetype into a unique handle 302 * @param cpHandle content provider handle 303 * @param messageType message type (TYPE_MMS/TYPE_SMS_GSM/TYPE_SMS_CDMA/TYPE_EMAIL) 304 * @return String Formatted Map Handle 305 */ getMapConvoHandle(long cpHandle, TYPE messageType)306 public static String getMapConvoHandle(long cpHandle, TYPE messageType) { 307 String mapHandle = "-1"; 308 switch (messageType) { 309 case MMS: 310 case SMS_GSM: 311 case SMS_CDMA: 312 mapHandle = getLongLongAsString(cpHandle, CONVO_ID_TYPE_SMS_MMS); 313 break; 314 case EMAIL: 315 case IM: 316 mapHandle = getLongLongAsString(cpHandle, CONVO_ID_TYPE_EMAIL_IM); 317 break; 318 default: 319 throw new IllegalArgumentException("Message type not supported"); 320 } 321 return mapHandle; 322 323 } 324 325 /** 326 * Convert a handle string the the raw long representation, including the type bit. 327 * @param mapHandle the handle string 328 * @return the handle value 329 */ getMsgHandleAsLong(String mapHandle)330 public static long getMsgHandleAsLong(String mapHandle) { 331 return Long.parseLong(mapHandle, 16); 332 } 333 334 /** 335 * Convert a Map Handle into a content provider Handle 336 * @param mapHandle handle to convert from 337 * @return content provider handle without message type mask 338 */ getCpHandle(String mapHandle)339 public static long getCpHandle(String mapHandle) { 340 long cpHandle = getMsgHandleAsLong(mapHandle); 341 if (D) { 342 Log.d(TAG, "-> MAP handle:" + mapHandle); 343 } 344 /* remove masks as the call should already know what type of message this handle is for */ 345 cpHandle &= ~HANDLE_TYPE_MASK; 346 if (D) { 347 Log.d(TAG, "->CP handle:" + cpHandle); 348 } 349 350 return cpHandle; 351 } 352 353 /** 354 * Extract the message type from the handle. 355 * @param mapHandle 356 * @return 357 */ getMsgTypeFromHandle(String mapHandle)358 public static TYPE getMsgTypeFromHandle(String mapHandle) { 359 long cpHandle = getMsgHandleAsLong(mapHandle); 360 361 if ((cpHandle & HANDLE_TYPE_MMS_MASK) != 0) { 362 return TYPE.MMS; 363 } 364 if ((cpHandle & HANDLE_TYPE_EMAIL_MASK) != 0) { 365 return TYPE.EMAIL; 366 } 367 if ((cpHandle & HANDLE_TYPE_SMS_GSM_MASK) != 0) { 368 return TYPE.SMS_GSM; 369 } 370 if ((cpHandle & HANDLE_TYPE_SMS_CDMA_MASK) != 0) { 371 return TYPE.SMS_CDMA; 372 } 373 if ((cpHandle & HANDLE_TYPE_IM_MASK) != 0) { 374 return TYPE.IM; 375 } 376 377 throw new IllegalArgumentException("Message type not found in handle string."); 378 } 379 380 /** 381 * TODO: Is this still needed after changing to another XML encoder? It should escape illegal 382 * characters. 383 * Strip away any illegal XML characters, that would otherwise cause the 384 * xml serializer to throw an exception. 385 * Examples of such characters are the emojis used on Android. 386 * @param text The string to validate 387 * @return the same string if valid, otherwise a new String stripped for 388 * any illegal characters. If a null pointer is passed an empty string will be returned. 389 */ stripInvalidChars(String text)390 public static String stripInvalidChars(String text) { 391 if (text == null) { 392 return ""; 393 } 394 char[] out = new char[text.length()]; 395 int i, o, l; 396 for (i = 0, o = 0, l = text.length(); i < l; i++) { 397 char c = text.charAt(i); 398 if ((c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd)) { 399 out[o++] = c; 400 } // Else we skip the character 401 } 402 403 if (i == o) { 404 return text; 405 } else { // We removed some characters, create the new string 406 return new String(out, 0, o); 407 } 408 } 409 410 /** 411 * Truncate UTF-8 string encoded byte array to desired length 412 * @param utf8String String to convert to bytes array h 413 * @param maxLength Max length of byte array returned including null termination 414 * @return byte array containing valid utf8 characters with max length 415 * @throws UnsupportedEncodingException 416 */ truncateUtf8StringToBytearray(String utf8String, int maxLength)417 public static byte[] truncateUtf8StringToBytearray(String utf8String, int maxLength) 418 throws UnsupportedEncodingException { 419 420 byte[] utf8Bytes = new byte[utf8String.length() + 1]; 421 try { 422 System.arraycopy(utf8String.getBytes("UTF-8"), 0, utf8Bytes, 0, utf8String.length()); 423 } catch (UnsupportedEncodingException e) { 424 Log.e(TAG, "truncateUtf8StringToBytearray: getBytes exception ", e); 425 throw e; 426 } 427 428 if (utf8Bytes.length > maxLength) { 429 /* if 'continuation' byte is in place 200, 430 * then strip previous bytes until utf-8 start byte is found */ 431 if ((utf8Bytes[maxLength - 1] & 0xC0) == 0x80) { 432 for (int i = maxLength - 2; i >= 0; i--) { 433 if ((utf8Bytes[i] & 0xC0) == 0xC0) { 434 /* first byte in utf-8 character found, 435 * now copy i - 1 bytes to outBytes and add null termination */ 436 utf8Bytes = Arrays.copyOf(utf8Bytes, i + 1); 437 utf8Bytes[i] = 0; 438 break; 439 } 440 } 441 } else { 442 /* copy bytes to outBytes and null terminate */ 443 utf8Bytes = Arrays.copyOf(utf8Bytes, maxLength); 444 utf8Bytes[maxLength - 1] = 0; 445 } 446 } 447 return utf8Bytes; 448 } 449 450 private static final Pattern PATTERN = Pattern.compile("=\\?(.+?)\\?(.)\\?(.+?(?=\\?=))\\?="); 451 452 /** 453 * Method for converting quoted printable og base64 encoded string from headers. 454 * @param in the string with encoding 455 * @return decoded string if success - else the same string as was as input. 456 */ stripEncoding(String in)457 public static String stripEncoding(String in) { 458 String str = null; 459 if (in.contains("=?") && in.contains("?=")) { 460 String encoding; 461 String charset; 462 String encodedText; 463 String match; 464 Matcher m = PATTERN.matcher(in); 465 while (m.find()) { 466 match = m.group(0); 467 charset = m.group(1); 468 encoding = m.group(2); 469 encodedText = m.group(3); 470 Log.v(TAG, 471 "Matching:" + match + "\nCharset: " + charset + "\nEncoding : " + encoding 472 + "\nText: " + encodedText); 473 if (encoding.equalsIgnoreCase("Q")) { 474 //quoted printable 475 Log.d(TAG, "StripEncoding: Quoted Printable string : " + encodedText); 476 str = new String(quotedPrintableToUtf8(encodedText, charset)); 477 in = in.replace(match, str); 478 } else if (encoding.equalsIgnoreCase("B")) { 479 // base64 480 try { 481 482 Log.d(TAG, "StripEncoding: base64 string : " + encodedText); 483 str = new String( 484 Base64.decode(encodedText.getBytes(charset), Base64.DEFAULT), 485 charset); 486 Log.d(TAG, "StripEncoding: decoded string : " + str); 487 in = in.replace(match, str); 488 } catch (UnsupportedEncodingException e) { 489 Log.e(TAG, "stripEncoding: Unsupported charset: " + charset); 490 } catch (IllegalArgumentException e) { 491 Log.e(TAG, "stripEncoding: string not encoded as base64: " + encodedText); 492 } 493 } else { 494 Log.e(TAG, "stripEncoding: Hit unknown encoding: " + encoding); 495 } 496 } 497 } 498 return in; 499 } 500 501 502 /** 503 * Convert a quoted-printable encoded string to a UTF-8 string: 504 * - Remove any soft line breaks: "=<CRLF>" 505 * - Convert all "=xx" to the corresponding byte 506 * @param text quoted-printable encoded UTF-8 text 507 * @return decoded UTF-8 string 508 */ quotedPrintableToUtf8(String text, String charset)509 public static byte[] quotedPrintableToUtf8(String text, String charset) { 510 byte[] output = new byte[text.length()]; // We allocate for the worst case memory need 511 byte[] input = null; 512 try { 513 input = text.getBytes("US-ASCII"); 514 } catch (UnsupportedEncodingException e) { 515 /* This cannot happen as "US-ASCII" is supported for all Java implementations */ 516 } 517 518 if (input == null) { 519 return "".getBytes(); 520 } 521 522 int in, out, stopCnt = input.length - 2; // Leave room for peaking the next two bytes 523 524 /* Algorithm: 525 * - Search for token, copying all non token chars 526 * */ 527 for (in = 0, out = 0; in < stopCnt; in++) { 528 byte b0 = input[in]; 529 if (b0 == '=') { 530 byte b1 = input[++in]; 531 byte b2 = input[++in]; 532 if (b1 == '\r' && b2 == '\n') { 533 continue; // soft line break, remove all tree; 534 } 535 if (((b1 >= '0' && b1 <= '9') || (b1 >= 'A' && b1 <= 'F') || (b1 >= 'a' 536 && b1 <= 'f')) && ((b2 >= '0' && b2 <= '9') || (b2 >= 'A' && b2 <= 'F') || ( 537 b2 >= 'a' && b2 <= 'f'))) { 538 if (V) { 539 Log.v(TAG, "Found hex number: " + String.format("%c%c", b1, b2)); 540 } 541 if (b1 <= '9') { 542 b1 = (byte) (b1 - '0'); 543 } else if (b1 <= 'F') { 544 b1 = (byte) (b1 - 'A' + 10); 545 } else if (b1 <= 'f') { 546 b1 = (byte) (b1 - 'a' + 10); 547 } 548 549 if (b2 <= '9') { 550 b2 = (byte) (b2 - '0'); 551 } else if (b2 <= 'F') { 552 b2 = (byte) (b2 - 'A' + 10); 553 } else if (b2 <= 'f') { 554 b2 = (byte) (b2 - 'a' + 10); 555 } 556 557 if (V) { 558 Log.v(TAG, 559 "Resulting nibble values: " + String.format("b1=%x b2=%x", b1, b2)); 560 } 561 562 output[out++] = (byte) (b1 << 4 | b2); // valid hex char, append 563 if (V) { 564 Log.v(TAG, "Resulting value: " + String.format("0x%2x", output[out - 1])); 565 } 566 continue; 567 } 568 Log.w(TAG, "Received wrongly quoted printable encoded text. " 569 + "Continuing at best effort..."); 570 /* If we get a '=' without either a hex value or CRLF following, just add it and 571 * rewind the in counter. */ 572 output[out++] = b0; 573 in -= 2; 574 continue; 575 } else { 576 output[out++] = b0; 577 continue; 578 } 579 } 580 581 // Just add any remaining characters. If they contain any encoding, it is invalid, 582 // and best effort would be just to display the characters. 583 while (in < input.length) { 584 output[out++] = input[in++]; 585 } 586 587 String result = null; 588 // Figure out if we support the charset, else fall back to UTF-8, as this is what 589 // the MAP specification suggest to use, and is compatible with US-ASCII. 590 if (charset == null) { 591 charset = "UTF-8"; 592 } else { 593 charset = charset.toUpperCase(); 594 try { 595 if (!Charset.isSupported(charset)) { 596 charset = "UTF-8"; 597 } 598 } catch (IllegalCharsetNameException e) { 599 Log.w(TAG, "Received unknown charset: " + charset + " - using UTF-8."); 600 charset = "UTF-8"; 601 } 602 } 603 try { 604 result = new String(output, 0, out, charset); 605 } catch (UnsupportedEncodingException e) { 606 /* This cannot happen unless Charset.isSupported() is out of sync with String */ 607 try { 608 result = new String(output, 0, out, "UTF-8"); 609 } catch (UnsupportedEncodingException e2) { 610 Log.e(TAG, "quotedPrintableToUtf8: " + e); 611 } 612 } 613 return result.getBytes(); /* return the result as "UTF-8" bytes */ 614 } 615 616 /** 617 * Encodes an array of bytes into an array of quoted-printable 7-bit characters. 618 * Unsafe characters are escaped. 619 * Simplified version of encoder from QuetedPrintableCodec.java (Apache external) 620 * 621 * @param bytes 622 * array of bytes to be encoded 623 * @return UTF-8 string containing quoted-printable characters 624 */ 625 626 private static final byte ESCAPE_CHAR = '='; 627 private static final byte TAB = 9; 628 private static final byte SPACE = 32; 629 encodeQuotedPrintable(byte[] bytes)630 public static final String encodeQuotedPrintable(byte[] bytes) { 631 if (bytes == null) { 632 return null; 633 } 634 635 BitSet printable = new BitSet(256); 636 // alpha characters 637 for (int i = 33; i <= 60; i++) { 638 printable.set(i); 639 } 640 for (int i = 62; i <= 126; i++) { 641 printable.set(i); 642 } 643 printable.set(TAB); 644 printable.set(SPACE); 645 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 646 for (int i = 0; i < bytes.length; i++) { 647 int b = bytes[i]; 648 if (b < 0) { 649 b = 256 + b; 650 } 651 if (printable.get(b)) { 652 buffer.write(b); 653 } else { 654 buffer.write(ESCAPE_CHAR); 655 char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16)); 656 char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16)); 657 buffer.write(hex1); 658 buffer.write(hex2); 659 } 660 } 661 try { 662 return buffer.toString("UTF-8"); 663 } catch (UnsupportedEncodingException e) { 664 //cannot happen 665 return ""; 666 } 667 } 668 669 getDateTimeString( long timestamp)670 static String getDateTimeString( long timestamp) { 671 SimpleDateFormat format = (mPeerSupportUtcTimeStamp) ? new 672 SimpleDateFormat("yyyyMMdd'T'HHmmssZ") : new SimpleDateFormat("yyyyMMdd'T'HHmmss"); 673 Calendar cal = Calendar.getInstance(); 674 cal.setTimeInMillis(timestamp); 675 if (V) Log.v(TAG, "getDateTimeString timestamp :" + timestamp + " time:" 676 + format.format(cal.getTime())); 677 return format.format(cal.getTime()); 678 } 679 savePeerSupportUtcTimeStamp(int remoteFeatureMask)680 static void savePeerSupportUtcTimeStamp(int remoteFeatureMask) { 681 if ((remoteFeatureMask & MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT) 682 == MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT) { 683 mPeerSupportUtcTimeStamp = true; 684 } else { 685 mPeerSupportUtcTimeStamp = false; 686 } 687 if (V) Log.v(TAG, "savePeerSupportUtcTimeStamp " + mPeerSupportUtcTimeStamp); 688 } 689 690 } 691 692