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