1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.mtp; 18 19import android.content.Context; 20import android.database.Cursor; 21import android.mtp.MtpObjectInfo; 22import android.net.Uri; 23import android.provider.DocumentsContract; 24import android.provider.DocumentsContract.Document; 25import android.test.AndroidTestCase; 26import android.test.suitebuilder.annotation.MediumTest; 27 28import java.io.IOException; 29import java.util.HashMap; 30import java.util.Map; 31import java.util.concurrent.CountDownLatch; 32import java.util.concurrent.TimeoutException; 33 34@MediumTest 35public class DocumentLoaderTest extends AndroidTestCase { 36 private MtpDatabase mDatabase; 37 private BlockableTestMtpManager mManager; 38 private TestContentResolver mResolver; 39 private DocumentLoader mLoader; 40 final private Identifier mParentIdentifier = new Identifier( 41 0, 0, 0, "2", MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE); 42 43 @Override 44 public void setUp() throws Exception { 45 mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 46 47 mDatabase.getMapper().startAddingDocuments(null); 48 mDatabase.getMapper().putDeviceDocument( 49 new MtpDeviceRecord(0, "Device", null, true, new MtpRoot[0], null, null)); 50 mDatabase.getMapper().stopAddingDocuments(null); 51 52 mDatabase.getMapper().startAddingDocuments("1"); 53 mDatabase.getMapper().putStorageDocuments("1", new int[0], new MtpRoot[] { 54 new MtpRoot(0, 0, "Storage", 1000, 1000, "") 55 }); 56 mDatabase.getMapper().stopAddingDocuments("1"); 57 58 mManager = new BlockableTestMtpManager(getContext()); 59 mResolver = new TestContentResolver(); 60 } 61 62 @Override 63 public void tearDown() throws Exception { 64 mLoader.close(); 65 mDatabase.close(); 66 } 67 68 public void testBasic() throws Exception { 69 setUpLoader(); 70 71 final Uri uri = DocumentsContract.buildChildDocumentsUri( 72 MtpDocumentsProvider.AUTHORITY, mParentIdentifier.mDocumentId); 73 setUpDocument(mManager, 40); 74 mManager.blockDocument(0, 15); 75 mManager.blockDocument(0, 35); 76 77 { 78 final Cursor cursor = mLoader.queryChildDocuments( 79 MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier); 80 assertEquals(DocumentLoader.NUM_INITIAL_ENTRIES, cursor.getCount()); 81 } 82 83 Thread.sleep(DocumentLoader.NOTIFY_PERIOD_MS); 84 mManager.unblockDocument(0, 15); 85 mResolver.waitForNotification(uri, 1); 86 87 { 88 final Cursor cursor = mLoader.queryChildDocuments( 89 MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier); 90 assertEquals( 91 DocumentLoader.NUM_INITIAL_ENTRIES + DocumentLoader.NUM_LOADING_ENTRIES, 92 cursor.getCount()); 93 } 94 95 mManager.unblockDocument(0, 35); 96 mResolver.waitForNotification(uri, 2); 97 98 { 99 final Cursor cursor = mLoader.queryChildDocuments( 100 MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier); 101 assertEquals(40, cursor.getCount()); 102 } 103 104 assertEquals(2, mResolver.getChangeCount(uri)); 105 } 106 107 public void testError_GetObjectHandles() throws Exception { 108 mManager = new BlockableTestMtpManager(getContext()) { 109 @Override 110 int[] getObjectHandles(int deviceId, int storageId, int parentObjectHandle) 111 throws IOException { 112 throw new IOException(); 113 } 114 }; 115 setUpLoader(); 116 mManager.setObjectHandles(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, null); 117 try { 118 try (final Cursor cursor = mLoader.queryChildDocuments( 119 MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) {} 120 fail(); 121 } catch (IOException exception) { 122 // Expect exception. 123 } 124 } 125 126 public void testError_GetObjectInfo() throws Exception { 127 mManager = new BlockableTestMtpManager(getContext()) { 128 @Override 129 MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { 130 if (objectHandle == DocumentLoader.NUM_INITIAL_ENTRIES) { 131 throw new IOException(); 132 } else { 133 return super.getObjectInfo(deviceId, objectHandle); 134 } 135 } 136 }; 137 setUpLoader(); 138 setUpDocument(mManager, DocumentLoader.NUM_INITIAL_ENTRIES); 139 try (final Cursor cursor = mLoader.queryChildDocuments( 140 MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) { 141 // Even if MtpManager returns an error for a document, loading must complete. 142 assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING)); 143 } 144 } 145 146 public void testCancelTask() throws IOException, InterruptedException, TimeoutException { 147 setUpDocument(mManager, 148 DocumentLoader.NUM_INITIAL_ENTRIES + 1); 149 150 // Block the first iteration in the background thread. 151 mManager.blockDocument( 152 0, DocumentLoader.NUM_INITIAL_ENTRIES + 1); 153 setUpLoader(); 154 try (final Cursor cursor = mLoader.queryChildDocuments( 155 MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) { 156 assertTrue(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING)); 157 } 158 159 final Uri uri = DocumentsContract.buildChildDocumentsUri( 160 MtpDocumentsProvider.AUTHORITY, mParentIdentifier.mDocumentId); 161 assertEquals(0, mResolver.getChangeCount(uri)); 162 163 // Clear task while the first iteration is being blocked. 164 mLoader.cancelTask(mParentIdentifier); 165 mManager.unblockDocument( 166 0, DocumentLoader.NUM_INITIAL_ENTRIES + 1); 167 Thread.sleep(DocumentLoader.NOTIFY_PERIOD_MS); 168 assertEquals(0, mResolver.getChangeCount(uri)); 169 170 // Check if it's OK to query invalidated task. 171 try (final Cursor cursor = mLoader.queryChildDocuments( 172 MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) { 173 assertTrue(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING)); 174 } 175 mResolver.waitForNotification(uri, 1); 176 } 177 178 private void setUpLoader() { 179 mLoader = new DocumentLoader( 180 new MtpDeviceRecord( 181 0, "Device", "Key", true, new MtpRoot[0], 182 TestUtil.OPERATIONS_SUPPORTED, new int[0]), 183 mManager, 184 mResolver, 185 mDatabase); 186 } 187 188 private void setUpDocument(TestMtpManager manager, int count) { 189 int[] childDocuments = new int[count]; 190 for (int i = 0; i < childDocuments.length; i++) { 191 final int objectHandle = i + 1; 192 childDocuments[i] = objectHandle; 193 manager.setObjectInfo(0, new MtpObjectInfo.Builder() 194 .setObjectHandle(objectHandle) 195 .setName(Integer.toString(i)) 196 .build()); 197 } 198 manager.setObjectHandles(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, childDocuments); 199 } 200 201 private static class BlockableTestMtpManager extends TestMtpManager { 202 final private Map<String, CountDownLatch> blockedDocuments = new HashMap<>(); 203 204 BlockableTestMtpManager(Context context) { 205 super(context); 206 } 207 208 void blockDocument(int deviceId, int objectHandle) { 209 blockedDocuments.put(pack(deviceId, objectHandle), new CountDownLatch(1)); 210 } 211 212 void unblockDocument(int deviceId, int objectHandle) { 213 blockedDocuments.get(pack(deviceId, objectHandle)).countDown(); 214 } 215 216 @Override 217 MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { 218 final CountDownLatch latch = blockedDocuments.get(pack(deviceId, objectHandle)); 219 if (latch != null) { 220 try { 221 latch.await(); 222 } catch(InterruptedException e) { 223 fail(); 224 } 225 } 226 return super.getObjectInfo(deviceId, objectHandle); 227 } 228 } 229} 230