1adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project/** 2adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project******************************************************************************* 3adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project* Copyright (C) 1996-2006, International Business Machines Corporation and * 4adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project* others. All Rights Reserved. * 5adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project******************************************************************************* 6adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project* 7adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project******************************************************************************* 8f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes*/ 9f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes /** 10adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * A JNI interface for ICU converters. 11adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * 12f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes * 13adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * @author Ram Viswanadha, IBM 14adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project */ 153664d8839f0ba794f428119ee7f7304a66861da5Elliott Hughespackage java.nio.charset; 16adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project 177365de1056414750d0a7d1fdd26025fd247f0d04Jesse Wilsonimport java.nio.ByteBuffer; 18adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.nio.CharBuffer; 195ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughesimport libcore.icu.ICU; 203664d8839f0ba794f428119ee7f7304a66861da5Elliott Hughesimport libcore.icu.NativeConverter; 2126ce8fbd8fe488cc969b08f64c56525662763dc4Jesse Wilsonimport libcore.util.EmptyArray; 22adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project 234fd8ac297725190c6d81e9538b1faf7cfb0f5bb8Elliott Hughesfinal class CharsetDecoderICU extends CharsetDecoder { 24cf6c3a752da274cc5025191d3bcd62e6222f4a4cElliott Hughes private static final int MAX_CHARS_PER_BYTE = 2; 25adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project 26c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes private static final int INPUT_OFFSET = 0; 27c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes private static final int OUTPUT_OFFSET = 1; 28bd024cc687470a008999d96fd3af4a1261413d55Narayan Kamath private static final int INVALID_BYTE_COUNT = 2; 29c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes /* 30c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes * data[INPUT_OFFSET] = on input contains the start of input and on output the number of input bytes consumed 31c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes * data[OUTPUT_OFFSET] = on input contains the start of output and on output the number of output chars written 32bd024cc687470a008999d96fd3af4a1261413d55Narayan Kamath * data[INVALID_BYTE_COUNT] = number of invalid bytes 33adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project */ 34bd024cc687470a008999d96fd3af4a1261413d55Narayan Kamath private final int[] data = new int[3]; 35f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes 36adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project /* handle to the ICU converter that is opened */ 37c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes private long converterHandle = 0; 38adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project 39c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes private byte[] input = null; 40c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes private char[] output= null; 419932735d0306721523082f77b0a9ba4aa4db8cdbMihai Preda 429932735d0306721523082f77b0a9ba4aa4db8cdbMihai Preda private byte[] allocatedInput = null; 439932735d0306721523082f77b0a9ba4aa4db8cdbMihai Preda private char[] allocatedOutput = null; 44f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes 45866e7ae17a3da81a02b0b144e0c9c2b3196d293aElliott Hughes // These instance variables are always assigned in the methods before being used. This class 46866e7ae17a3da81a02b0b144e0c9c2b3196d293aElliott Hughes // is inherently thread-unsafe so we don't have to worry about synchronization. 47adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project private int inEnd; 48adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project private int outEnd; 49f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes 507d52d787302b862019da41aa753646d88d43fd61Elliott Hughes public static CharsetDecoderICU newInstance(Charset cs, String icuCanonicalName) { 517d52d787302b862019da41aa753646d88d43fd61Elliott Hughes // This complexity is necessary to ensure that even if the constructor, superclass 527d52d787302b862019da41aa753646d88d43fd61Elliott Hughes // constructor, or call to updateCallback throw, we still free the native peer. 537d52d787302b862019da41aa753646d88d43fd61Elliott Hughes long address = 0; 547d52d787302b862019da41aa753646d88d43fd61Elliott Hughes try { 557d52d787302b862019da41aa753646d88d43fd61Elliott Hughes address = NativeConverter.openConverter(icuCanonicalName); 567d52d787302b862019da41aa753646d88d43fd61Elliott Hughes float averageCharsPerByte = NativeConverter.getAveCharsPerByte(address); 577d52d787302b862019da41aa753646d88d43fd61Elliott Hughes CharsetDecoderICU result = new CharsetDecoderICU(cs, averageCharsPerByte, address); 587d52d787302b862019da41aa753646d88d43fd61Elliott Hughes address = 0; // CharsetDecoderICU has taken ownership; its finalizer will do the free. 597d52d787302b862019da41aa753646d88d43fd61Elliott Hughes result.updateCallback(); 607d52d787302b862019da41aa753646d88d43fd61Elliott Hughes return result; 617d52d787302b862019da41aa753646d88d43fd61Elliott Hughes } finally { 627d52d787302b862019da41aa753646d88d43fd61Elliott Hughes if (address != 0) { 637d52d787302b862019da41aa753646d88d43fd61Elliott Hughes NativeConverter.closeConverter(address); 647d52d787302b862019da41aa753646d88d43fd61Elliott Hughes } 657d52d787302b862019da41aa753646d88d43fd61Elliott Hughes } 667d52d787302b862019da41aa753646d88d43fd61Elliott Hughes } 677d52d787302b862019da41aa753646d88d43fd61Elliott Hughes 687d52d787302b862019da41aa753646d88d43fd61Elliott Hughes private CharsetDecoderICU(Charset cs, float averageCharsPerByte, long address) { 697d52d787302b862019da41aa753646d88d43fd61Elliott Hughes super(cs, averageCharsPerByte, MAX_CHARS_PER_BYTE); 707d52d787302b862019da41aa753646d88d43fd61Elliott Hughes this.converterHandle = address; 711bdc6bc1c72b033ed860360e69fc9f1f1121579bNarayan Kamath NativeConverter.registerConverter(this, converterHandle); 72adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 73f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes 742981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes @Override protected void implReplaceWith(String newReplacement) { 7537871fb106b08055ad56d7f04d4faccdd163e1afElliott Hughes updateCallback(); 76adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 77f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes 782981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes @Override protected final void implOnMalformedInput(CodingErrorAction newAction) { 79cf6c3a752da274cc5025191d3bcd62e6222f4a4cElliott Hughes updateCallback(); 80adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 81f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes 822981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes @Override protected final void implOnUnmappableCharacter(CodingErrorAction newAction) { 83cf6c3a752da274cc5025191d3bcd62e6222f4a4cElliott Hughes updateCallback(); 84cf6c3a752da274cc5025191d3bcd62e6222f4a4cElliott Hughes } 85cf6c3a752da274cc5025191d3bcd62e6222f4a4cElliott Hughes 86cf6c3a752da274cc5025191d3bcd62e6222f4a4cElliott Hughes private void updateCallback() { 875ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughes NativeConverter.setCallbackDecode(converterHandle, this); 88adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 89f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes 902981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes @Override protected void implReset() { 912981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes NativeConverter.resetByteToChar(converterHandle); 922981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes data[INPUT_OFFSET] = 0; 932981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes data[OUTPUT_OFFSET] = 0; 94bd024cc687470a008999d96fd3af4a1261413d55Narayan Kamath data[INVALID_BYTE_COUNT] = 0; 952981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes output = null; 962981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes input = null; 972981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes allocatedInput = null; 982981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes allocatedOutput = null; 992981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes inEnd = 0; 1002981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes outEnd = 0; 1012981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes } 1022981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes 103dbf863488607fbb16a3d28c09f772d9581bd64adElliott Hughes @Override protected final CoderResult implFlush(CharBuffer out) { 104c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes try { 105dbf863488607fbb16a3d28c09f772d9581bd64adElliott Hughes // ICU needs to see an empty input. 106dbf863488607fbb16a3d28c09f772d9581bd64adElliott Hughes input = EmptyArray.BYTE; 107dbf863488607fbb16a3d28c09f772d9581bd64adElliott Hughes inEnd = 0; 108dbf863488607fbb16a3d28c09f772d9581bd64adElliott Hughes data[INPUT_OFFSET] = 0; 109dbf863488607fbb16a3d28c09f772d9581bd64adElliott Hughes 110c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes data[OUTPUT_OFFSET] = getArray(out); 111bd024cc687470a008999d96fd3af4a1261413d55Narayan Kamath data[INVALID_BYTE_COUNT] = 0; // Make sure we don't see earlier errors. 112dbf863488607fbb16a3d28c09f772d9581bd64adElliott Hughes 1135ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughes int error = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, true); 1145ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughes if (ICU.U_FAILURE(error)) { 1155ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughes if (error == ICU.U_BUFFER_OVERFLOW_ERROR) { 116adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project return CoderResult.OVERFLOW; 1175ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughes } else if (error == ICU.U_TRUNCATED_CHAR_FOUND) { 118bd024cc687470a008999d96fd3af4a1261413d55Narayan Kamath if (data[INVALID_BYTE_COUNT] > 0) { 119bd024cc687470a008999d96fd3af4a1261413d55Narayan Kamath return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]); 120adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 121adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 122adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 123adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project return CoderResult.UNDERFLOW; 124c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes } finally { 125adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project setPosition(out); 126adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project implReset(); 127adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 128adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 129f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes 130dbf863488607fbb16a3d28c09f772d9581bd64adElliott Hughes @Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) { 131dbf863488607fbb16a3d28c09f772d9581bd64adElliott Hughes if (!in.hasRemaining()) { 132adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project return CoderResult.UNDERFLOW; 133adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 134adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project 135adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project data[INPUT_OFFSET] = getArray(in); 136adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project data[OUTPUT_OFFSET]= getArray(out); 137f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes 1382981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes try { 1395ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughes int error = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, false); 1405ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughes if (ICU.U_FAILURE(error)) { 1415ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughes if (error == ICU.U_BUFFER_OVERFLOW_ERROR) { 1425ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughes return CoderResult.OVERFLOW; 1435ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughes } else if (error == ICU.U_INVALID_CHAR_FOUND) { 144bd024cc687470a008999d96fd3af4a1261413d55Narayan Kamath return CoderResult.unmappableForLength(data[INVALID_BYTE_COUNT]); 1455ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughes } else if (error == ICU.U_ILLEGAL_CHAR_FOUND) { 146bd024cc687470a008999d96fd3af4a1261413d55Narayan Kamath return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]); 1475ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughes } else { 1485ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughes throw new AssertionError(error); 1495ec69b20ab9b3e2dcbe225d548168b09afbbbac2Elliott Hughes } 150adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 151c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes // Decoding succeeded: give us more data. 152adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project return CoderResult.UNDERFLOW; 153c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes } finally { 154adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project setPosition(in); 155adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project setPosition(out); 156adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 157adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 158f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes 159f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes 160866e7ae17a3da81a02b0b144e0c9c2b3196d293aElliott Hughes private int getArray(CharBuffer out) { 161c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes if (out.hasArray()) { 162adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project output = out.array(); 163b7bfb47e9720ecc6e10f43878f27e40542a9c800Elliott Hughes outEnd = out.arrayOffset() + out.limit(); 164b7bfb47e9720ecc6e10f43878f27e40542a9c800Elliott Hughes return out.arrayOffset() + out.position(); 165c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes } else { 166adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project outEnd = out.remaining(); 1672981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes if (allocatedOutput == null || outEnd > allocatedOutput.length) { 1689932735d0306721523082f77b0a9ba4aa4db8cdbMihai Preda allocatedOutput = new char[outEnd]; 169adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 1702981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes // The array's start position is 0. 1719932735d0306721523082f77b0a9ba4aa4db8cdbMihai Preda output = allocatedOutput; 172adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project return 0; 173adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 174adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 175c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes 176866e7ae17a3da81a02b0b144e0c9c2b3196d293aElliott Hughes private int getArray(ByteBuffer in) { 177c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes if (in.hasArray()) { 178adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project input = in.array(); 179b7bfb47e9720ecc6e10f43878f27e40542a9c800Elliott Hughes inEnd = in.arrayOffset() + in.limit(); 1802981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes return in.arrayOffset() + in.position(); 181c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes } else { 182adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project inEnd = in.remaining(); 1832981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes if (allocatedInput == null || inEnd > allocatedInput.length) { 1849932735d0306721523082f77b0a9ba4aa4db8cdbMihai Preda allocatedInput = new byte[inEnd]; 185adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 1862981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes // Copy the input buffer into the allocated array. 187adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project int pos = in.position(); 1882981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes in.get(allocatedInput, 0, inEnd); 189adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project in.position(pos); 1902981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes // The array's start position is 0. 1912981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes input = allocatedInput; 1922981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes return 0; 193adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 194adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 195c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes 196866e7ae17a3da81a02b0b144e0c9c2b3196d293aElliott Hughes private void setPosition(CharBuffer out) { 197c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes if (out.hasArray()) { 198654e8191641085aa6bff6df6aaef9a8cb0c03ec1Narayan Kamath out.position(out.position() + data[OUTPUT_OFFSET]); 199c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes } else { 200c60bc1815dca549f3fb4e572f6aac749d7fa9fc6Elliott Hughes out.put(output, 0, data[OUTPUT_OFFSET]); 201adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 2029932735d0306721523082f77b0a9ba4aa4db8cdbMihai Preda // release reference to output array, which may not be ours 2039932735d0306721523082f77b0a9ba4aa4db8cdbMihai Preda output = null; 204adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 205adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project 206866e7ae17a3da81a02b0b144e0c9c2b3196d293aElliott Hughes private void setPosition(ByteBuffer in) { 2072981b5e8cf7c19dfd85b2088b18b7a6146825317Elliott Hughes in.position(in.position() + data[INPUT_OFFSET]); 2089932735d0306721523082f77b0a9ba4aa4db8cdbMihai Preda // release reference to input array, which may not be ours 2099932735d0306721523082f77b0a9ba4aa4db8cdbMihai Preda input = null; 210adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project } 211adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project} 212