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
17package com.android.documentsui.files;
18
19import static com.android.documentsui.testing.IntentAsserts.assertHasAction;
20import static com.android.documentsui.testing.IntentAsserts.assertHasExtraIntent;
21import static com.android.documentsui.testing.IntentAsserts.assertHasExtraList;
22import static com.android.documentsui.testing.IntentAsserts.assertHasExtraUri;
23import static org.junit.Assert.assertEquals;
24import static org.junit.Assert.assertFalse;
25import static org.junit.Assert.assertNotNull;
26import static org.junit.Assert.assertNull;
27import static org.junit.Assert.assertSame;
28import static org.junit.Assert.assertTrue;
29
30import android.app.Activity;
31import android.app.PendingIntent;
32import android.content.ClipData;
33import android.content.Intent;
34import android.net.Uri;
35import android.os.Parcelable;
36import android.provider.DocumentsContract;
37import android.provider.DocumentsContract.Path;
38import android.support.test.filters.MediumTest;
39import android.support.test.InstrumentationRegistry;
40import android.support.test.runner.AndroidJUnit4;
41import android.util.Pair;
42import android.view.DragEvent;
43
44import com.android.documentsui.AbstractActionHandler;
45import com.android.documentsui.R;
46import com.android.documentsui.TestActionModeAddons;
47import com.android.documentsui.archives.ArchivesProvider;
48import com.android.documentsui.base.DocumentInfo;
49import com.android.documentsui.base.DocumentStack;
50import com.android.documentsui.base.RootInfo;
51import com.android.documentsui.base.Shared;
52import com.android.documentsui.testing.ClipDatas;
53import com.android.documentsui.testing.DocumentStackAsserts;
54import com.android.documentsui.testing.Roots;
55import com.android.documentsui.testing.TestActivityConfig;
56import com.android.documentsui.testing.TestDocumentClipper;
57import com.android.documentsui.testing.TestDragAndDropManager;
58import com.android.documentsui.testing.TestEnv;
59import com.android.documentsui.testing.TestProvidersAccess;
60import com.android.documentsui.ui.TestDialogController;
61
62import org.junit.Before;
63import org.junit.Test;
64import org.junit.runner.RunWith;
65
66import java.util.Arrays;
67
68@RunWith(AndroidJUnit4.class)
69@MediumTest
70public class ActionHandlerTest {
71
72    private TestEnv mEnv;
73    private TestActivity mActivity;
74    private TestActionModeAddons mActionModeAddons;
75    private TestDialogController mDialogs;
76    private ActionHandler<TestActivity> mHandler;
77    private TestDocumentClipper mClipper;
78    private TestDragAndDropManager mDragAndDropManager;
79    private boolean refreshAnswer = false;
80
81    @Before
82    public void setUp() {
83        mEnv = TestEnv.create();
84        mActivity = TestActivity.create(mEnv);
85        mActionModeAddons = new TestActionModeAddons();
86        mDialogs = new TestDialogController();
87        mClipper = new TestDocumentClipper();
88        mDragAndDropManager = new TestDragAndDropManager();
89
90        mEnv.providers.configurePm(mActivity.packageMgr);
91        ((TestActivityConfig) mEnv.injector.config).nextDocumentEnabled = true;
92        mEnv.injector.dialogs = mDialogs;
93
94        mHandler = createHandler();
95
96        mDialogs.confirmNext();
97
98        mEnv.selectDocument(TestEnv.FILE_GIF);
99    }
100
101    @Test
102    public void testOpenSelectedInNewWindow() {
103        mHandler.openSelectedInNewWindow();
104
105        DocumentStack path = new DocumentStack(Roots.create("123"), mEnv.model.getDocument("1"));
106
107        Intent expected = LauncherActivity.createLaunchIntent(mActivity);
108        expected.putExtra(Shared.EXTRA_STACK, (Parcelable) path);
109
110        Intent actual = mActivity.startActivity.getLastValue();
111        assertEquals(expected.toString(), actual.toString());
112    }
113
114    @Test
115    public void testSpringOpenDirectory() {
116        mHandler.springOpenDirectory(TestEnv.FOLDER_0);
117        assertTrue(mActionModeAddons.finishActionModeCalled);
118        assertEquals(TestEnv.FOLDER_0, mEnv.state.stack.peek());
119    }
120
121    @Test
122    public void testCutSelectedDocuments_NoGivenSelection() {
123        mEnv.populateStack();
124
125        mEnv.selectionMgr.clearSelection();
126        mHandler.cutToClipboard();
127        mDialogs.assertDocumentsClippedNotShown();
128    }
129
130    @Test
131    public void testCutSelectedDocuments_ContainsNonMovableItem() {
132        mEnv.populateStack();
133        mEnv.selectDocument(TestEnv.FILE_READ_ONLY);
134
135        mHandler.cutToClipboard();
136        mDialogs.assertDocumentsClippedNotShown();
137        mDialogs.assertShowOperationUnsupported();
138        mClipper.clipForCut.assertNotCalled();
139    }
140
141    @Test
142    public void testCopySelectedDocuments_NoGivenSelection() {
143        mEnv.populateStack();
144
145        mEnv.selectionMgr.clearSelection();
146        mHandler.copyToClipboard();
147        mDialogs.assertDocumentsClippedNotShown();
148    }
149
150    @Test
151    public void testDeleteSelectedDocuments_NoSelection() {
152        mEnv.populateStack();
153
154        mEnv.selectionMgr.clearSelection();
155        mHandler.deleteSelectedDocuments();
156        mDialogs.assertNoFileFailures();
157        mActivity.startService.assertNotCalled();
158        mActionModeAddons.finishOnConfirmed.assertNeverCalled();
159    }
160
161    @Test
162    public void testDeleteSelectedDocuments_Cancelable() {
163        mEnv.populateStack();
164
165        mDialogs.rejectNext();
166        mHandler.deleteSelectedDocuments();
167        mDialogs.assertNoFileFailures();
168        mActivity.startService.assertNotCalled();
169        mActionModeAddons.finishOnConfirmed.assertRejected();
170    }
171
172    // Recents root means when deleting the srcParent will be null.
173    @Test
174    public void testDeleteSelectedDocuments_RecentsRoot() {
175        mEnv.state.stack.changeRoot(TestProvidersAccess.RECENTS);
176
177        mHandler.deleteSelectedDocuments();
178        mDialogs.assertNoFileFailures();
179        mActivity.startService.assertCalled();
180        mActionModeAddons.finishOnConfirmed.assertCalled();
181    }
182
183    @Test
184    public void testShareSelectedDocuments_ShowsChooser() {
185        mActivity.resources.strings.put(R.string.share_via, "Sharezilla!");
186        mHandler.shareSelectedDocuments();
187
188        mActivity.assertActivityStarted(Intent.ACTION_CHOOSER);
189    }
190
191    @Test
192    public void testShareSelectedDocuments_Single() {
193        mActivity.resources.strings.put(R.string.share_via, "Sharezilla!");
194        mHandler.shareSelectedDocuments();
195
196        Intent intent = assertHasExtraIntent(mActivity.startActivity.getLastValue());
197        assertHasAction(intent, Intent.ACTION_SEND);
198        assertFalse(intent.hasCategory(Intent.CATEGORY_TYPED_OPENABLE));
199        assertFalse(intent.hasCategory(Intent.CATEGORY_OPENABLE));
200        assertHasExtraUri(intent, Intent.EXTRA_STREAM);
201    }
202
203    @Test
204    public void testShareSelectedDocuments_ArchivedFile() {
205        mEnv = TestEnv.create(ArchivesProvider.AUTHORITY);
206        mHandler = createHandler();
207
208        mActivity.resources.strings.put(R.string.share_via, "Sharezilla!");
209        mEnv.selectionMgr.clearSelection();
210        mEnv.selectDocument(TestEnv.FILE_PDF);
211        mHandler.shareSelectedDocuments();
212
213        Intent intent = mActivity.startActivity.getLastValue();
214        assertNull(intent);
215    }
216
217    @Test
218    public void testShareSelectedDocuments_Multiple() {
219        mActivity.resources.strings.put(R.string.share_via, "Sharezilla!");
220        mEnv.selectDocument(TestEnv.FILE_PDF);
221        mHandler.shareSelectedDocuments();
222
223        Intent intent = assertHasExtraIntent(mActivity.startActivity.getLastValue());
224        assertHasAction(intent, Intent.ACTION_SEND_MULTIPLE);
225        assertFalse(intent.hasCategory(Intent.CATEGORY_TYPED_OPENABLE));
226        assertFalse(intent.hasCategory(Intent.CATEGORY_OPENABLE));
227        assertHasExtraList(intent, Intent.EXTRA_STREAM, 2);
228    }
229
230    @Test
231    public void testShareSelectedDocuments_VirtualFiles() {
232        if (!mEnv.features.isVirtualFilesSharingEnabled()) {
233            return;
234        }
235
236        mActivity.resources.strings.put(R.string.share_via, "Sharezilla!");
237        mEnv.selectionMgr.clearSelection();
238        mEnv.selectDocument(TestEnv.FILE_VIRTUAL);
239        mHandler.shareSelectedDocuments();
240
241        Intent intent = assertHasExtraIntent(mActivity.startActivity.getLastValue());
242        assertHasAction(intent, Intent.ACTION_SEND);
243        assertTrue(intent.hasCategory(Intent.CATEGORY_TYPED_OPENABLE));
244        assertFalse(intent.hasCategory(Intent.CATEGORY_OPENABLE));
245        assertHasExtraUri(intent, Intent.EXTRA_STREAM);
246    }
247
248    @Test
249    public void testShareSelectedDocuments_RegularAndVirtualFiles() {
250        mActivity.resources.strings.put(R.string.share_via, "Sharezilla!");
251        mEnv.selectDocument(TestEnv.FILE_PNG);
252        mEnv.selectDocument(TestEnv.FILE_VIRTUAL);
253        mHandler.shareSelectedDocuments();
254
255        Intent intent = assertHasExtraIntent(mActivity.startActivity.getLastValue());
256        assertHasAction(intent, Intent.ACTION_SEND_MULTIPLE);
257
258        assertFalse(intent.hasCategory(Intent.CATEGORY_OPENABLE));
259        if (mEnv.features.isVirtualFilesSharingEnabled()) {
260            assertTrue(intent.hasCategory(Intent.CATEGORY_TYPED_OPENABLE));
261            assertHasExtraList(intent, Intent.EXTRA_STREAM, 3);
262        }else {
263            assertHasExtraList(intent, Intent.EXTRA_STREAM, 2);
264        }
265    }
266
267    @Test
268    public void testShareSelectedDocuments_OmitsPartialFiles() {
269        mActivity.resources.strings.put(R.string.share_via, "Sharezilla!");
270        mEnv.selectDocument(TestEnv.FILE_PARTIAL);
271        mEnv.selectDocument(TestEnv.FILE_PNG);
272        mHandler.shareSelectedDocuments();
273
274        Intent intent = assertHasExtraIntent(mActivity.startActivity.getLastValue());
275        assertHasAction(intent, Intent.ACTION_SEND_MULTIPLE);
276        assertFalse(intent.hasCategory(Intent.CATEGORY_TYPED_OPENABLE));
277        assertFalse(intent.hasCategory(Intent.CATEGORY_OPENABLE));
278        assertHasExtraList(intent, Intent.EXTRA_STREAM, 2);
279    }
280
281    @Test
282    public void testDocumentPicked_DefaultsToView() throws Exception {
283        mActivity.currentRoot = TestProvidersAccess.HOME;
284
285        mHandler.openDocument(TestEnv.FILE_GIF, ActionHandler.VIEW_TYPE_PREVIEW,
286                ActionHandler.VIEW_TYPE_REGULAR);
287        mActivity.assertActivityStarted(Intent.ACTION_VIEW);
288    }
289
290    @Test
291    public void testDocumentPicked_InArchive_QuickViewable() throws Exception {
292        mActivity.resources.setQuickViewerPackage("corptropolis.viewer");
293        mActivity.currentRoot = TestProvidersAccess.HOME;
294
295        mHandler.openDocument(TestEnv.FILE_IN_ARCHIVE, ActionHandler.VIEW_TYPE_PREVIEW,
296                ActionHandler.VIEW_TYPE_REGULAR);
297        mActivity.assertActivityStarted(Intent.ACTION_QUICK_VIEW);
298    }
299
300    @Test
301    public void testDocumentPicked_InArchive_Unopenable() throws Exception {
302        mActivity.currentRoot = TestProvidersAccess.HOME;
303
304        mHandler.openDocument(TestEnv.FILE_IN_ARCHIVE, ActionHandler.VIEW_TYPE_PREVIEW,
305                ActionHandler.VIEW_TYPE_REGULAR);
306        mDialogs.assertViewInArchivesShownUnsupported();
307    }
308
309    @Test
310    public void testDocumentPicked_PreviewsWhenResourceSet() throws Exception {
311        mActivity.resources.setQuickViewerPackage("corptropolis.viewer");
312        mActivity.currentRoot = TestProvidersAccess.HOME;
313
314        mHandler.openDocument(TestEnv.FILE_GIF, ActionHandler.VIEW_TYPE_PREVIEW,
315                ActionHandler.VIEW_TYPE_REGULAR);
316        mActivity.assertActivityStarted(Intent.ACTION_QUICK_VIEW);
317    }
318
319    @Test
320    public void testDocumentPicked_Downloads_ManagesApks() throws Exception {
321        mActivity.currentRoot = TestProvidersAccess.DOWNLOADS;
322
323        mHandler.openDocument(TestEnv.FILE_APK, ActionHandler.VIEW_TYPE_PREVIEW,
324                ActionHandler.VIEW_TYPE_REGULAR);
325        mActivity.assertActivityStarted(DocumentsContract.ACTION_MANAGE_DOCUMENT);
326    }
327
328    @Test
329    public void testDocumentPicked_Downloads_ManagesPartialFiles() throws Exception {
330        mActivity.currentRoot = TestProvidersAccess.DOWNLOADS;
331
332        mHandler.openDocument(TestEnv.FILE_PARTIAL, ActionHandler.VIEW_TYPE_PREVIEW,
333                ActionHandler.VIEW_TYPE_REGULAR);
334        mActivity.assertActivityStarted(DocumentsContract.ACTION_MANAGE_DOCUMENT);
335    }
336
337    @Test
338    public void testDocumentPicked_OpensArchives() throws Exception {
339        mActivity.currentRoot = TestProvidersAccess.HOME;
340        mEnv.docs.nextDocument = TestEnv.FILE_ARCHIVE;
341
342        mHandler.openDocument(TestEnv.FILE_ARCHIVE, ActionHandler.VIEW_TYPE_PREVIEW,
343                ActionHandler.VIEW_TYPE_REGULAR);
344        assertEquals(TestEnv.FILE_ARCHIVE, mEnv.state.stack.peek());
345    }
346
347    @Test
348    public void testDocumentPicked_OpensDirectories() throws Exception {
349        mActivity.currentRoot = TestProvidersAccess.HOME;
350
351        mHandler.openDocument(TestEnv.FOLDER_1, ActionHandler.VIEW_TYPE_PREVIEW,
352                ActionHandler.VIEW_TYPE_REGULAR);
353        assertEquals(TestEnv.FOLDER_1, mEnv.state.stack.peek());
354    }
355
356    @Test
357    public void testShowChooser() throws Exception {
358        mActivity.currentRoot = TestProvidersAccess.DOWNLOADS;
359
360        mHandler.showChooserForDoc(TestEnv.FILE_PDF);
361        mActivity.assertActivityStarted(Intent.ACTION_CHOOSER);
362    }
363
364    @Test
365    public void testInitLocation_RestoresIfStackIsLoaded() throws Exception {
366        mEnv.state.stack.changeRoot(TestProvidersAccess.DOWNLOADS);
367        mEnv.state.stack.push(TestEnv.FOLDER_0);
368
369        mHandler.initLocation(mActivity.getIntent());
370        mActivity.restoreRootAndDirectory.assertCalled();
371    }
372
373    @Test
374    public void testInitLocation_LoadsRootDocIfStackOnlyHasRoot() throws Exception {
375        mEnv.state.stack.changeRoot(TestProvidersAccess.HAMMY);
376
377        mHandler.initLocation(mActivity.getIntent());
378        assertRootPicked(TestProvidersAccess.HAMMY.getUri());
379    }
380
381    @Test
382    public void testInitLocation_DefaultsToDownloads() throws Exception {
383        mActivity.resources.bools.put(R.bool.show_documents_root, false);
384
385        mHandler.initLocation(mActivity.getIntent());
386        assertRootPicked(TestProvidersAccess.DOWNLOADS.getUri());
387    }
388
389    @Test
390    public void testInitLocation_DocumentsRootEnabled() throws Exception {
391        mActivity.resources.bools.put(R.bool.show_documents_root, true);
392        mActivity.resources.strings.put(R.string.default_root_uri, TestProvidersAccess.HOME.getUri().toString());
393
394        mHandler.initLocation(mActivity.getIntent());
395        assertRootPicked(TestProvidersAccess.HOME.getUri());
396    }
397
398    @Test
399    public void testInitLocation_BrowseRoot() throws Exception {
400        Intent intent = mActivity.getIntent();
401        intent.setAction(Intent.ACTION_VIEW);
402        intent.setData(TestProvidersAccess.PICKLES.getUri());
403
404        mHandler.initLocation(intent);
405        assertRootPicked(TestProvidersAccess.PICKLES.getUri());
406    }
407
408    @Test
409    public void testInitLocation_LaunchToDocuments() throws Exception {
410        if (!mEnv.features.isLaunchToDocumentEnabled()) {
411            return;
412        }
413
414        mEnv.docs.nextIsDocumentsUri = true;
415        mEnv.docs.nextPath = new Path(
416                TestProvidersAccess.HOME.rootId,
417                Arrays.asList(
418                        TestEnv.FOLDER_0.documentId,
419                        TestEnv.FOLDER_1.documentId));
420        mEnv.docs.nextDocuments =
421                Arrays.asList(TestEnv.FOLDER_0, TestEnv.FOLDER_1);
422
423        mActivity.refreshCurrentRootAndDirectory.assertNotCalled();
424        Intent intent = mActivity.getIntent();
425        intent.setAction(Intent.ACTION_VIEW);
426        intent.setData(TestEnv.FOLDER_1.derivedUri);
427        mHandler.initLocation(intent);
428
429        mEnv.beforeAsserts();
430
431        DocumentStackAsserts.assertEqualsTo(mEnv.state.stack, TestProvidersAccess.HOME,
432                Arrays.asList(TestEnv.FOLDER_0, TestEnv.FOLDER_1));
433        mActivity.refreshCurrentRootAndDirectory.assertCalled();
434    }
435
436    @Test
437    public void testDragAndDrop_OnReadOnlyRoot() throws Exception {
438        RootInfo root = new RootInfo(); // root by default has no SUPPORT_CREATE flag
439        DragEvent event = DragEvent.obtain(DragEvent.ACTION_DROP, 1, 1, null, null, null,
440                null, true);
441        assertFalse(mHandler.dropOn(event, root));
442    }
443
444    @Test
445    public void testDragAndDrop_OnLibraryRoot() throws Exception {
446        DragEvent event = DragEvent.obtain(DragEvent.ACTION_DROP, 1, 1, null, null, null,
447                null, true);
448        assertFalse(mHandler.dropOn(event, TestProvidersAccess.RECENTS));
449    }
450
451    @Test
452    public void testDragAndDrop_DropsOnWritableRoot() throws Exception {
453        // DragEvent gets recycled in Android, so it is possible that by the time the callback is
454        // called, event.getLocalState() and event.getClipData() returns null. This tests to ensure
455        // our Clipper is getting the original CipData passed in.
456        Object localState = new Object();
457        ClipData clipData = ClipDatas.createTestClipData();
458        DragEvent event = DragEvent.obtain(DragEvent.ACTION_DROP, 1, 1, localState, null, clipData,
459                null, true);
460
461        mHandler.dropOn(event, TestProvidersAccess.DOWNLOADS);
462        event.recycle();
463
464        Pair<ClipData, RootInfo> actual = mDragAndDropManager.dropOnRootHandler.getLastValue();
465        assertSame(clipData, actual.first);
466        assertSame(TestProvidersAccess.DOWNLOADS, actual.second);
467    }
468
469    @Test
470    public void testRefresh_nullUri() throws Exception {
471        refreshAnswer = true;
472        mHandler.refreshDocument(null, (boolean answer) -> {
473            refreshAnswer = answer;
474        });
475
476        mEnv.beforeAsserts();
477        assertFalse(refreshAnswer);
478    }
479
480    @Test
481    public void testRefresh_emptyStack() throws Exception {
482        refreshAnswer = true;
483        assertTrue(mEnv.state.stack.isEmpty());
484        mHandler.refreshDocument(new DocumentInfo(), (boolean answer) -> {
485            refreshAnswer = answer;
486        });
487
488        mEnv.beforeAsserts();
489        assertFalse(refreshAnswer);
490    }
491
492    @Test
493    public void testRefresh() throws Exception {
494        refreshAnswer = false;
495        mEnv.populateStack();
496        mHandler.refreshDocument(mEnv.model.getDocument("1"), (boolean answer) -> {
497            refreshAnswer = answer;
498        });
499
500        mEnv.beforeAsserts();
501        if (mEnv.features.isContentRefreshEnabled()) {
502            assertTrue(refreshAnswer);
503        } else {
504            assertFalse(refreshAnswer);
505        }
506    }
507
508    @Test
509    public void testAuthentication() throws Exception {
510        PendingIntent intent = PendingIntent.getActivity(
511                InstrumentationRegistry.getInstrumentation().getTargetContext(), 0, new Intent(),
512                0);
513
514        mHandler.startAuthentication(intent);
515        assertEquals(intent.getIntentSender(), mActivity.startIntentSender.getLastValue().first);
516        assertEquals(AbstractActionHandler.CODE_AUTHENTICATION,
517                mActivity.startIntentSender.getLastValue().second.intValue());
518    }
519
520    @Test
521    public void testOnActivityResult_onOK() throws Exception {
522        mHandler.onActivityResult(AbstractActionHandler.CODE_AUTHENTICATION, Activity.RESULT_OK,
523                null);
524        mActivity.refreshCurrentRootAndDirectory.assertCalled();
525    }
526
527    @Test
528    public void testOnActivityResult_onNotOK() throws Exception {
529        mHandler.onActivityResult(0, Activity.RESULT_OK, null);
530        mActivity.refreshCurrentRootAndDirectory.assertNotCalled();
531
532        mHandler.onActivityResult(AbstractActionHandler.CODE_AUTHENTICATION,
533                Activity.RESULT_CANCELED, null);
534        mActivity.refreshCurrentRootAndDirectory.assertNotCalled();
535    }
536
537    private void assertRootPicked(Uri expectedUri) throws Exception {
538        mEnv.beforeAsserts();
539
540        mActivity.rootPicked.assertCalled();
541        RootInfo root = mActivity.rootPicked.getLastValue();
542        assertNotNull(root);
543        assertEquals(expectedUri, root.getUri());
544    }
545
546    private ActionHandler<TestActivity> createHandler() {
547        return new ActionHandler<>(
548                mActivity,
549                mEnv.state,
550                mEnv.providers,
551                mEnv.docs,
552                mEnv.searchViewManager,
553                mEnv::lookupExecutor,
554                mActionModeAddons,
555                mClipper,
556                null,  // clip storage, not utilized unless we venture into *jumbo* clip territory.
557                mDragAndDropManager,
558                mEnv.injector
559        );
560    }
561}
562