140ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen/* 240ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * Copyright (C) 2010 The Android Open Source Project 340ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * 440ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * Licensed under the Apache License, Version 2.0 (the "License"); 540ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * you may not use this file except in compliance with the License. 640ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * You may obtain a copy of the License at 740ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * 840ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * http://www.apache.org/licenses/LICENSE-2.0 940ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * 1040ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * Unless required by applicable law or agreed to in writing, software 1140ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * distributed under the License is distributed on an "AS IS" BASIS, 1240ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1340ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * See the License for the specific language governing permissions and 1440ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * limitations under the License. 1540ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen */ 1640ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen 1731fd85f39b554e09b2e6c1c2ccf5c186859880faSteve Howardpackage android.app; 1840ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen 1931fd85f39b554e09b2e6c1c2ccf5c186859880faSteve Howardimport android.app.DownloadManager.Query; 2031fd85f39b554e09b2e6c1c2ccf5c186859880faSteve Howardimport android.app.DownloadManager.Request; 2140ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyenimport android.database.Cursor; 2231fd85f39b554e09b2e6c1c2ccf5c186859880faSteve Howardimport android.net.Uri; 236f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Noriimport android.os.Environment; 2440ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyenimport android.os.ParcelFileDescriptor; 256f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Noriimport android.os.StatFs; 2682e891b3259350a92b55969a6380ca1240ee0829Vasu Noriimport android.test.suitebuilder.annotation.LargeTest; 2740ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyenimport android.util.Log; 2840ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen 2982e891b3259350a92b55969a6380ca1240ee0829Vasu Noriimport java.io.File; 306f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Noriimport java.io.FileOutputStream; 316f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Noriimport java.io.IOException; 3282e891b3259350a92b55969a6380ca1240ee0829Vasu Noriimport java.util.Random; 3382e891b3259350a92b55969a6380ca1240ee0829Vasu Nori 346f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori/** 356f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori * Integration tests of the DownloadManager API. 366f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori */ 3740ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyenpublic class DownloadManagerStressTest extends DownloadManagerBaseTest { 386f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori private static final String TAG = "DownloadManagerStressTest"; 396f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori private final static String CACHE_DIR = 406f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori Environment.getDownloadCacheDirectory().getAbsolutePath(); 4140ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen 4240ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen /** 4340ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * {@inheritDoc} 4440ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen */ 4540ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen @Override 4640ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen public void setUp() throws Exception { 4740ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen super.setUp(); 48bd06f02d02e07ca15e420ee9e50e35253646ba64Neal Nguyen setWiFiStateOn(true); 4940ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen removeAllCurrentDownloads(); 5040ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen } 5140ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen 5240ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen /** 5382e891b3259350a92b55969a6380ca1240ee0829Vasu Nori * {@inheritDoc} 5482e891b3259350a92b55969a6380ca1240ee0829Vasu Nori */ 5582e891b3259350a92b55969a6380ca1240ee0829Vasu Nori @Override 5682e891b3259350a92b55969a6380ca1240ee0829Vasu Nori public void tearDown() throws Exception { 5782e891b3259350a92b55969a6380ca1240ee0829Vasu Nori super.tearDown(); 5882e891b3259350a92b55969a6380ca1240ee0829Vasu Nori setWiFiStateOn(true); 5982e891b3259350a92b55969a6380ca1240ee0829Vasu Nori removeAllCurrentDownloads(); 6082e891b3259350a92b55969a6380ca1240ee0829Vasu Nori 6182e891b3259350a92b55969a6380ca1240ee0829Vasu Nori if (mReceiver != null) { 6282e891b3259350a92b55969a6380ca1240ee0829Vasu Nori mContext.unregisterReceiver(mReceiver); 6382e891b3259350a92b55969a6380ca1240ee0829Vasu Nori mReceiver = null; 6482e891b3259350a92b55969a6380ca1240ee0829Vasu Nori } 6582e891b3259350a92b55969a6380ca1240ee0829Vasu Nori } 6682e891b3259350a92b55969a6380ca1240ee0829Vasu Nori 6782e891b3259350a92b55969a6380ca1240ee0829Vasu Nori /** 686f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori * Attempts to download several files simultaneously 6940ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen */ 706f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori @LargeTest 716f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori public void testMultipleDownloads() throws Exception { 7240ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen // need to be sure all current downloads have stopped first 736f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori removeAllCurrentDownloads(); 746f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori int NUM_FILES = 10; 756f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori int MAX_FILE_SIZE = 10 * 1024; // 10 kb 7640ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen 776f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori Random r = new LoggingRng(); 786f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori for (int i=0; i<NUM_FILES; ++i) { 796f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori int size = r.nextInt(MAX_FILE_SIZE); 806f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori byte[] blobData = generateData(size, DataType.TEXT); 8140ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen 826f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori Uri uri = getServerUri(DEFAULT_FILENAME + i); 836f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori Request request = new Request(uri); 846f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori request.setTitle(String.format("%s--%d", DEFAULT_FILENAME + i, i)); 8540ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen 866f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori // Prepare the mock server with a standard response 87b14ad8cc8cb0ed774072b077694b21fd0a6f33beJeff Sharkey enqueueResponse(buildResponse(HTTP_OK, blobData)); 8840ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen 896f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori long requestID = mDownloadManager.enqueue(request); 906f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori } 916f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori 926f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori waitForDownloadsOrTimeout(WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME); 936f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori Cursor cursor = mDownloadManager.query(new Query()); 946f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori try { 9540ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen assertEquals(NUM_FILES, cursor.getCount()); 966f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori 976f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori if (cursor.moveToFirst()) { 986f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori do { 996f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori int status = cursor.getInt(cursor.getColumnIndex( 1006f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori DownloadManager.COLUMN_STATUS)); 1016f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori String filename = cursor.getString(cursor.getColumnIndex( 1026f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori DownloadManager.COLUMN_URI)); 1036f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori String errorString = String.format( 1046f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori "File %s failed to download successfully. Status code: %d", 1056f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori filename, status); 1066f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori assertEquals(errorString, DownloadManager.STATUS_SUCCESSFUL, status); 1076f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori } while (cursor.moveToNext()); 10840ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen } 1096f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori 1106f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori assertEquals(NUM_FILES, mReceiver.numDownloadsCompleted()); 11140ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen } finally { 1126f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori cursor.close(); 11340ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen } 11440ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen } 11540ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen /** 11640ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen * Tests trying to download a large file (50M bytes). 11740ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen */ 11882e891b3259350a92b55969a6380ca1240ee0829Vasu Nori @LargeTest 11940ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen public void testDownloadLargeFile() throws Exception { 12040ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen long fileSize = 50000000L; // note: kept relatively small to not exceed /cache dir size 1216f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori Log.i(TAG, "creating a file of size: " + fileSize); 12240ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen File largeFile = createFileOnSD(null, fileSize, DataType.TEXT, null); 1236f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori Log.i(TAG, "DONE creating a file of size: " + fileSize); 12440ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen MultipleDownloadsCompletedReceiver receiver = registerNewMultipleDownloadsReceiver(); 12540ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen 12640ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen try { 12740ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen long dlRequest = doStandardEnqueue(largeFile); 12840ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen 129b14ad8cc8cb0ed774072b077694b21fd0a6f33beJeff Sharkey // wait for the download to complete 13040ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen waitForDownloadOrTimeout(dlRequest); 13140ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen 13240ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest); 13340ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen verifyFileContents(pfd, largeFile); 13440ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen verifyFileSize(pfd, largeFile.length()); 13540ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen 13640ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen assertEquals(1, receiver.numDownloadsCompleted()); 13740ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen mContext.unregisterReceiver(receiver); 13840ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen } catch (Exception e) { 13940ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen throw e; 14040ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen } finally { 14140ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen largeFile.delete(); 14240ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen } 14340ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen } 1446f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori 1456f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori 1466f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori /** 1476f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori * Tests downloading a file to system cache when there isn't enough space in the system cache 1486f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori * to hold the entire file. DownloadManager deletes enough files to make space for the 1496f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori * new download. 1506f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori */ 1516f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori @LargeTest 1526f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori public void testDownloadToCacheWithAlmostFullCache() throws Exception { 1536f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori int DOWNLOAD_FILE_SIZE = 1024 * 1024; // 1MB 1546f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori 1556f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori StatFs fs = new StatFs(CACHE_DIR); 1566f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori int blockSize = fs.getBlockSize(); 1576f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori int availableBlocks = fs.getAvailableBlocks(); 1586f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori int availableBytes = blockSize * availableBlocks; 1596f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori Log.i(TAG, "INITIAL stage, available space in /cache: " + availableBytes); 1606f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori File outFile = File.createTempFile("DM_TEST", null, new File(CACHE_DIR)); 1616f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori byte[] buffer = new byte[blockSize]; 1626f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori 1636f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori try { 1646f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori // fill cache to ensure we don't have enough space - take half the size of the 1656f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori // download size, and leave that much freespace left on the cache partition 1666f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori if (DOWNLOAD_FILE_SIZE <= availableBytes) { 1676f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori int writeSizeBytes = availableBytes - (DOWNLOAD_FILE_SIZE / 2); 1686f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori 1696f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori int writeSizeBlocks = writeSizeBytes / blockSize; 1706f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori int remainderSizeBlocks = availableBlocks - writeSizeBlocks; 1716f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori 1726f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori FileOutputStream fo = null; 1736f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori try { 1746f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori fo = new FileOutputStream(outFile); 1756f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori while (fs.getAvailableBlocks() >= remainderSizeBlocks) { 1766f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori fo.write(buffer); 1776f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori fs.restat(CACHE_DIR); 1786f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori } 1796f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori } catch (IOException e) { 1806f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori Log.e(LOG_TAG, "error filling file: ", e); 1816f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori throw e; 1826f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori } finally { 1836f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori if (fo != null) { 1846f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori fo.close(); 1856f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori } 1866f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori } 1876f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori } 1886f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori 1896f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori // /cache should now be almost full. 1906f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori long spaceAvailable = fs.getAvailableBlocks() * blockSize; 1916f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori Log.i(TAG, "BEFORE download, available space in /cache: " + spaceAvailable); 1926f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori assertTrue(DOWNLOAD_FILE_SIZE > spaceAvailable); 1936f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori 1946f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori // try to download 1MB file into /cache - and it should succeed 1956f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori byte[] blobData = generateData(DOWNLOAD_FILE_SIZE, DataType.TEXT); 1966f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori long dlRequest = doBasicDownload(blobData, DOWNLOAD_TO_SYSTEM_CACHE); 1976f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori verifyAndCleanupSingleFileDownload(dlRequest, blobData); 1986f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori } finally { 1996f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori if (outFile != null) { 2006f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori outFile.delete(); 2016f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori } 2026f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori } 2036f35c0ecba6cb1ce5d7563a9962acf9557dbacedVasu Nori } 20440ef0f49ea9fa7c39eb0018fdb4df4b73a11a77dNeal Nguyen} 205