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