1 /* 2 * Copyright (C) 2016 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 17 package com.android.documentsui; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static junit.framework.Assert.assertTrue; 22 import static junit.framework.Assert.fail; 23 24 import static org.junit.Assert.assertEquals; 25 import static org.mockito.Mockito.when; 26 27 import android.content.Intent; 28 import android.net.Uri; 29 import android.os.Parcelable; 30 import android.provider.DocumentsContract; 31 import android.provider.DocumentsContract.Path; 32 33 import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; 34 import androidx.test.filters.MediumTest; 35 import androidx.test.runner.AndroidJUnit4; 36 37 import com.android.documentsui.base.DocumentStack; 38 import com.android.documentsui.base.EventListener; 39 import com.android.documentsui.base.RootInfo; 40 import com.android.documentsui.base.Shared; 41 import com.android.documentsui.base.State; 42 import com.android.documentsui.files.LauncherActivity; 43 import com.android.documentsui.sorting.SortDimension; 44 import com.android.documentsui.sorting.SortModel; 45 import com.android.documentsui.testing.DocumentStackAsserts; 46 import com.android.documentsui.testing.Roots; 47 import com.android.documentsui.testing.TestEnv; 48 import com.android.documentsui.testing.TestEventHandler; 49 import com.android.documentsui.testing.TestProvidersAccess; 50 import com.android.documentsui.testing.UserManagers; 51 52 import org.junit.Before; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 56 import java.util.Arrays; 57 import java.util.concurrent.CountDownLatch; 58 import java.util.concurrent.TimeUnit; 59 60 /** 61 * A unit test *for* AbstractActionHandler, not an abstract test baseclass. 62 */ 63 @RunWith(AndroidJUnit4.class) 64 @MediumTest 65 public class AbstractActionHandlerTest { 66 67 private TestActivity mActivity; 68 private TestEnv mEnv; 69 private AbstractActionHandler<TestActivity> mHandler; 70 71 @Before setUp()72 public void setUp() { 73 mEnv = TestEnv.create(); 74 mActivity = TestActivity.create(mEnv); 75 mActivity.userManager = UserManagers.create(); 76 mHandler = new AbstractActionHandler<TestActivity>( 77 mActivity, 78 mEnv.state, 79 mEnv.providers, 80 mEnv.docs, 81 mEnv.searchViewManager, 82 mEnv::lookupExecutor, 83 mEnv.injector) { 84 85 @Override 86 public void openRoot(RootInfo root) { 87 throw new UnsupportedOperationException(); 88 } 89 90 @Override 91 public boolean openItem( 92 ItemDetails<String> doc, @ViewType int type, @ViewType int fallback) { 93 throw new UnsupportedOperationException(); 94 } 95 96 @Override 97 public void initLocation(Intent intent) { 98 throw new UnsupportedOperationException(); 99 } 100 101 @Override 102 protected void launchToDefaultLocation() { 103 throw new UnsupportedOperationException(); 104 } 105 }; 106 } 107 108 @Test testOpenNewWindow()109 public void testOpenNewWindow() { 110 DocumentStack path = new DocumentStack(Roots.create("123")); 111 mHandler.openInNewWindow(path); 112 113 Intent expected = LauncherActivity.createLaunchIntent(mActivity); 114 expected.putExtra(Shared.EXTRA_STACK, (Parcelable) path); 115 Intent actual = mActivity.startActivity.getLastValue(); 116 assertEquals(expected.toString(), actual.toString()); 117 } 118 119 @Test testOpensContainerDocuments_OpenFolderInSearch_JumpsToNewLocation()120 public void testOpensContainerDocuments_OpenFolderInSearch_JumpsToNewLocation() 121 throws Exception { 122 if (!mEnv.features.isLaunchToDocumentEnabled()) { 123 return; 124 } 125 126 mEnv.populateStack(); 127 128 mEnv.searchViewManager.isSearching = true; 129 mEnv.docs.nextIsDocumentsUri = true; 130 mEnv.docs.nextPath = new Path( 131 TestProvidersAccess.HOME.rootId, 132 Arrays.asList(TestEnv.FOLDER_1.documentId, TestEnv.FOLDER_2.documentId)); 133 mEnv.docs.nextDocuments = Arrays.asList(TestEnv.FOLDER_1, TestEnv.FOLDER_2); 134 135 mHandler.openContainerDocument(TestEnv.FOLDER_2); 136 137 mEnv.beforeAsserts(); 138 139 assertEquals(mEnv.docs.nextPath.getPath().size(), mEnv.state.stack.size()); 140 assertEquals(TestEnv.FOLDER_2, mEnv.state.stack.pop()); 141 assertEquals(TestEnv.FOLDER_1, mEnv.state.stack.pop()); 142 } 143 144 145 @Test testOpensContainerDocuments_ClickFolderInSearch_PushToRootDoc_NoFindPathSupport()146 public void testOpensContainerDocuments_ClickFolderInSearch_PushToRootDoc_NoFindPathSupport() 147 throws Exception { 148 mEnv.populateStack(); 149 150 mEnv.searchViewManager.isSearching = true; 151 mEnv.docs.nextIsDocumentsUri = true; 152 mEnv.docs.nextDocuments = Arrays.asList(TestEnv.FOLDER_1, TestEnv.FOLDER_2); 153 154 mHandler.openContainerDocument(TestEnv.FOLDER_2); 155 156 mEnv.beforeAsserts(); 157 158 assertEquals(2, mEnv.state.stack.size()); 159 assertEquals(TestEnv.FOLDER_2, mEnv.state.stack.pop()); 160 assertEquals(TestEnv.FOLDER_0, mEnv.state.stack.pop()); 161 } 162 163 @Test testOpensContainerDocuments_ClickArchiveInSearch_opensArchiveInArchiveProvider()164 public void testOpensContainerDocuments_ClickArchiveInSearch_opensArchiveInArchiveProvider() 165 throws Exception { 166 if (!mEnv.features.isLaunchToDocumentEnabled()) { 167 return; 168 } 169 170 mEnv.populateStack(); 171 172 mEnv.searchViewManager.isSearching = true; 173 mEnv.docs.nextIsDocumentsUri = true; 174 mEnv.docs.nextPath = new Path( 175 TestProvidersAccess.HOME.rootId, 176 Arrays.asList(TestEnv.FOLDER_1.documentId, TestEnv.FOLDER_2.documentId, 177 TestEnv.FILE_ARCHIVE.documentId)); 178 mEnv.docs.nextDocuments = Arrays.asList( 179 TestEnv.FOLDER_1, TestEnv.FOLDER_2, TestEnv.FILE_ARCHIVE); 180 mEnv.docs.nextDocument = TestEnv.FILE_IN_ARCHIVE; 181 182 mHandler.openContainerDocument(TestEnv.FILE_ARCHIVE); 183 184 mEnv.beforeAsserts(); 185 186 assertEquals(mEnv.docs.nextPath.getPath().size(), mEnv.state.stack.size()); 187 assertEquals(TestEnv.FILE_IN_ARCHIVE, mEnv.state.stack.pop()); 188 assertEquals(TestEnv.FOLDER_2, mEnv.state.stack.pop()); 189 assertEquals(TestEnv.FOLDER_1, mEnv.state.stack.pop()); 190 } 191 192 @Test testOpensDocument_ExceptionIfAlreadyInStack()193 public void testOpensDocument_ExceptionIfAlreadyInStack() throws Exception { 194 mEnv.populateStack(); 195 try { 196 mEnv.state.stack.push(TestEnv.FOLDER_0); 197 fail("Should have thrown IllegalArgumentException."); 198 } catch (IllegalArgumentException expected) { 199 } 200 } 201 202 @Test testLaunchToDocuments()203 public void testLaunchToDocuments() throws Exception { 204 if (!mEnv.features.isLaunchToDocumentEnabled()) { 205 return; 206 } 207 208 mEnv.docs.nextIsDocumentsUri = true; 209 mEnv.docs.nextPath = new Path( 210 TestProvidersAccess.HOME.rootId, 211 Arrays.asList( 212 TestEnv.FOLDER_0.documentId, 213 TestEnv.FOLDER_1.documentId, 214 TestEnv.FILE_GIF.documentId)); 215 mEnv.docs.nextDocuments = 216 Arrays.asList(TestEnv.FOLDER_0, TestEnv.FOLDER_1, TestEnv.FILE_GIF); 217 218 mActivity.refreshCurrentRootAndDirectory.assertNotCalled(); 219 assertTrue(mHandler.launchToDocument(TestEnv.FILE_GIF.derivedUri)); 220 221 mEnv.beforeAsserts(); 222 223 DocumentStackAsserts.assertEqualsTo(mEnv.state.stack, TestProvidersAccess.HOME, 224 Arrays.asList(TestEnv.FOLDER_0, TestEnv.FOLDER_1)); 225 mActivity.refreshCurrentRootAndDirectory.assertCalled(); 226 } 227 228 @Test testLaunchToDocuments_convertsTreeUriToDocumentUri()229 public void testLaunchToDocuments_convertsTreeUriToDocumentUri() throws Exception { 230 if (!mEnv.features.isLaunchToDocumentEnabled()) { 231 return; 232 } 233 234 mEnv.docs.nextIsDocumentsUri = true; 235 mEnv.docs.nextPath = new Path( 236 TestProvidersAccess.HOME.rootId, 237 Arrays.asList( 238 TestEnv.FOLDER_0.documentId, 239 TestEnv.FOLDER_1.documentId, 240 TestEnv.FILE_GIF.documentId)); 241 mEnv.docs.nextDocuments = 242 Arrays.asList(TestEnv.FOLDER_0, TestEnv.FOLDER_1, TestEnv.FILE_GIF); 243 244 final Uri treeBaseUri = DocumentsContract.buildTreeDocumentUri( 245 TestProvidersAccess.HOME.authority, TestEnv.FOLDER_0.documentId); 246 final Uri treeDocUri = DocumentsContract.buildDocumentUriUsingTree( 247 treeBaseUri, TestEnv.FILE_GIF.documentId); 248 assertTrue(mHandler.launchToDocument(treeDocUri)); 249 250 mEnv.beforeAsserts(); 251 252 DocumentStackAsserts.assertEqualsTo(mEnv.state.stack, TestProvidersAccess.HOME, 253 Arrays.asList(TestEnv.FOLDER_0, TestEnv.FOLDER_1)); 254 mEnv.docs.lastUri.assertLastArgument(TestEnv.FILE_GIF.derivedUri); 255 mActivity.refreshCurrentRootAndDirectory.assertCalled(); 256 } 257 258 @Test testLoadChildrenDocuments()259 public void testLoadChildrenDocuments() throws Exception { 260 mEnv.state.stack.changeRoot(TestProvidersAccess.HOME); 261 mEnv.state.stack.push(TestEnv.FOLDER_0); 262 263 mEnv.state.sortModel.sortByUser( 264 SortModel.SORT_DIMENSION_ID_TITLE, SortDimension.SORT_DIRECTION_ASCENDING); 265 266 mEnv.mockProviders.get(TestProvidersAccess.HOME.authority) 267 .setNextChildDocumentsReturns(TestEnv.FILE_APK, TestEnv.FILE_GIF); 268 269 mHandler.loadDocumentsForCurrentStack(); 270 CountDownLatch latch = new CountDownLatch(1); 271 mEnv.model.addUpdateListener(event -> latch.countDown()); 272 mActivity.supportLoaderManager.runAsyncTaskLoader(AbstractActionHandler.LOADER_ID); 273 274 latch.await(1, TimeUnit.SECONDS); 275 assertEquals(2, mEnv.model.getItemCount()); 276 String[] modelIds = mEnv.model.getModelIds(); 277 assertEquals(TestEnv.FILE_APK, mEnv.model.getDocument(modelIds[0])); 278 assertEquals(TestEnv.FILE_GIF, mEnv.model.getDocument(modelIds[1])); 279 } 280 281 @Test testCrossProfileDocuments_success()282 public void testCrossProfileDocuments_success() throws Exception { 283 mEnv.state.action = State.ACTION_GET_CONTENT; 284 mEnv.state.canShareAcrossProfile = true; 285 mEnv.state.stack.changeRoot(TestProvidersAccess.OtherUser.HOME); 286 mEnv.state.stack.push(TestEnv.OtherUser.FOLDER_0); 287 288 mEnv.state.sortModel.sortByUser( 289 SortModel.SORT_DIMENSION_ID_TITLE, SortDimension.SORT_DIRECTION_ASCENDING); 290 291 // Currently mock provider does not have cross profile concept, this will always return 292 // the supplied docs without checking for the user. But this should not be a problem for 293 // this test case. 294 mEnv.mockProviders.get(TestProvidersAccess.OtherUser.HOME.authority) 295 .setNextChildDocumentsReturns(TestEnv.OtherUser.FILE_PNG); 296 297 mHandler.loadDocumentsForCurrentStack(); 298 CountDownLatch latch = new CountDownLatch(1); 299 mEnv.model.addUpdateListener(event -> latch.countDown()); 300 mActivity.supportLoaderManager.runAsyncTaskLoader(AbstractActionHandler.LOADER_ID); 301 302 latch.await(1, TimeUnit.SECONDS); 303 assertEquals(1, mEnv.model.getItemCount()); 304 String[] modelIds = mEnv.model.getModelIds(); 305 assertEquals(TestEnv.OtherUser.FILE_PNG, mEnv.model.getDocument(modelIds[0])); 306 } 307 308 @Test testLoadCrossProfileDoc_failsWithQuietModeException()309 public void testLoadCrossProfileDoc_failsWithQuietModeException() throws Exception { 310 mEnv.state.action = State.ACTION_GET_CONTENT; 311 mEnv.state.canShareAcrossProfile = true; 312 mEnv.state.stack.changeRoot(TestProvidersAccess.OtherUser.HOME); 313 mEnv.state.stack.push(TestEnv.OtherUser.FOLDER_0); 314 // Turn off the other user. 315 when(mActivity.userManager.isQuietModeEnabled(TestProvidersAccess.OtherUser.USER_HANDLE)) 316 .thenReturn(true); 317 318 TestEventHandler<Model.Update> listener = new TestEventHandler<>(); 319 mEnv.model.addUpdateListener(listener::accept); 320 321 mHandler.loadDocumentsForCurrentStack(); 322 CountDownLatch latch = new CountDownLatch(1); 323 mEnv.model.addUpdateListener(event -> latch.countDown()); 324 mActivity.supportLoaderManager.runAsyncTaskLoader(AbstractActionHandler.LOADER_ID); 325 326 latch.await(1, TimeUnit.SECONDS); 327 assertThat(listener.getLastValue().getException()) 328 .isInstanceOf(CrossProfileQuietModeException.class); 329 } 330 331 @Test testLoadCrossProfileDoc_failsWithNoPermissionException()332 public void testLoadCrossProfileDoc_failsWithNoPermissionException() throws Exception { 333 mEnv.state.action = State.ACTION_GET_CONTENT; 334 mEnv.state.stack.changeRoot(TestProvidersAccess.OtherUser.HOME); 335 mEnv.state.stack.push(TestEnv.OtherUser.FOLDER_0); 336 // Disallow sharing across profile 337 mEnv.state.canShareAcrossProfile = false; 338 339 TestEventHandler<Model.Update> listener = new TestEventHandler<>(); 340 mEnv.model.addUpdateListener(listener::accept); 341 342 mHandler.loadDocumentsForCurrentStack(); 343 CountDownLatch latch = new CountDownLatch(1); 344 mEnv.model.addUpdateListener(event -> latch.countDown()); 345 mActivity.supportLoaderManager.runAsyncTaskLoader(AbstractActionHandler.LOADER_ID); 346 347 latch.await(1, TimeUnit.SECONDS); 348 assertThat(listener.getLastValue().getException()) 349 .isInstanceOf(CrossProfileNoPermissionException.class); 350 } 351 352 @Test testLoadCrossProfileDoc_bothError_showNoPermissionException()353 public void testLoadCrossProfileDoc_bothError_showNoPermissionException() throws Exception { 354 mEnv.state.action = State.ACTION_GET_CONTENT; 355 mEnv.state.stack.changeRoot(TestProvidersAccess.OtherUser.HOME); 356 mEnv.state.stack.push(TestEnv.OtherUser.FOLDER_0); 357 // Disallow sharing 358 mEnv.state.canShareAcrossProfile = false; 359 // Turn off the other user. 360 when(mActivity.userManager.isQuietModeEnabled(TestProvidersAccess.OtherUser.USER_HANDLE)) 361 .thenReturn(true); 362 363 TestEventHandler<Model.Update> listener = new TestEventHandler<>(); 364 mEnv.model.addUpdateListener(listener::accept); 365 366 mHandler.loadDocumentsForCurrentStack(); 367 CountDownLatch latch = new CountDownLatch(1); 368 mEnv.model.addUpdateListener(event -> latch.countDown()); 369 mActivity.supportLoaderManager.runAsyncTaskLoader(AbstractActionHandler.LOADER_ID); 370 371 latch.await(1, TimeUnit.SECONDS); 372 assertThat(listener.getLastValue().getException()) 373 .isInstanceOf(CrossProfileNoPermissionException.class); 374 } 375 376 @Test testCrossProfileDocuments_reloadSuccessAfterCrossProfileError()377 public void testCrossProfileDocuments_reloadSuccessAfterCrossProfileError() throws Exception { 378 mEnv.state.action = State.ACTION_GET_CONTENT; 379 mEnv.state.stack.changeRoot(TestProvidersAccess.OtherUser.HOME); 380 mEnv.state.stack.push(TestEnv.OtherUser.FOLDER_0); 381 382 mEnv.state.sortModel.sortByUser( 383 SortModel.SORT_DIMENSION_ID_TITLE, SortDimension.SORT_DIRECTION_ASCENDING); 384 385 // Currently mock provider does not have cross profile concept, this will always return 386 // the supplied docs without checking for the user. But this should not be a problem for 387 // this test case. 388 mEnv.mockProviders.get(TestProvidersAccess.OtherUser.HOME.authority) 389 .setNextChildDocumentsReturns(TestEnv.OtherUser.FILE_PNG); 390 391 // Disallow sharing across profile 392 mEnv.state.canShareAcrossProfile = false; 393 394 TestEventHandler<Model.Update> listener = new TestEventHandler<>(); 395 mEnv.model.addUpdateListener(listener::accept); 396 397 mHandler.loadDocumentsForCurrentStack(); 398 CountDownLatch latch1 = new CountDownLatch(1); 399 EventListener<Model.Update> updateEventListener1 = update -> latch1.countDown(); 400 mEnv.model.addUpdateListener(updateEventListener1); 401 mActivity.supportLoaderManager.runAsyncTaskLoader(AbstractActionHandler.LOADER_ID); 402 latch1.await(1, TimeUnit.SECONDS); 403 assertThat(listener.getLastValue().getException()) 404 .isInstanceOf(CrossProfileNoPermissionException.class); 405 406 // Allow sharing across profile. 407 mEnv.state.canShareAcrossProfile = true; 408 409 CountDownLatch latch2 = new CountDownLatch(1); 410 mEnv.model.addUpdateListener(update -> latch2.countDown()); 411 mHandler.loadDocumentsForCurrentStack(); 412 mActivity.supportLoaderManager.runAsyncTaskLoader(AbstractActionHandler.LOADER_ID); 413 414 latch2.await(1, TimeUnit.SECONDS); 415 assertEquals(1, mEnv.model.getItemCount()); 416 String[] modelIds = mEnv.model.getModelIds(); 417 assertEquals(TestEnv.OtherUser.FILE_PNG, mEnv.model.getDocument(modelIds[0])); 418 } 419 420 @Test testLoadChildrenDocuments_failsWithNonRecentsAndEmptyStack()421 public void testLoadChildrenDocuments_failsWithNonRecentsAndEmptyStack() throws Exception { 422 mEnv.state.stack.changeRoot(TestProvidersAccess.HOME); 423 424 mEnv.mockProviders.get(TestProvidersAccess.HOME.authority) 425 .setNextChildDocumentsReturns(TestEnv.FILE_APK, TestEnv.FILE_GIF); 426 427 TestEventHandler<Model.Update> listener = new TestEventHandler<>(); 428 mEnv.model.addUpdateListener(listener::accept); 429 430 mHandler.loadDocumentsForCurrentStack(); 431 CountDownLatch latch = new CountDownLatch(1); 432 mEnv.model.addUpdateListener(event -> latch.countDown()); 433 mActivity.supportLoaderManager.runAsyncTaskLoader(AbstractActionHandler.LOADER_ID); 434 435 latch.await(1, TimeUnit.SECONDS); 436 assertTrue(listener.getLastValue().hasException()); 437 } 438 439 @Test testPreviewItem_throwException()440 public void testPreviewItem_throwException() throws Exception { 441 try { 442 mHandler.previewItem(null); 443 fail("Should have thrown UnsupportedOperationException."); 444 } catch (UnsupportedOperationException expected) { 445 } 446 } 447 } 448