From 800300826ebb50f5a26393bc966c27fa26e9a16f Mon Sep 17 00:00:00 2001 From: Ben Lin Date: Mon, 1 May 2017 18:50:05 -0700 Subject: [PATCH] Proper refresh when Authentication finishes with Activity.RESULT_OK. Test: Updated DemoProvider, tested manually and added new unit tests. Bug: 37876119 Change-Id: I0a8f08ff06e536bf83fb4fcf5e91d765dfe5ccbb --- .../documentsui/AbstractActionHandler.java | 32 +++++++++++++++++ .../android/documentsui/ActionHandler.java | 10 ++++++ src/com/android/documentsui/BaseActivity.java | 5 +++ .../dirlist/DirectoryFragment.java | 5 +++ .../documentsui/dirlist/DocumentsAdapter.java | 2 ++ .../android/documentsui/dirlist/Message.java | 6 +--- .../documentsui/picker/ActionHandler.java | 34 +++++++++++++++++-- .../documentsui/picker/PickActivity.java | 33 ------------------ .../services/ResolvedResourcesJob.java | 1 - tests/AndroidManifest.xml | 7 ++++ .../documentsui/AuthenticationActivity.java | 31 +++++++++++++++++ .../com/android/documentsui/DemoProvider.java | 6 ++-- .../com/android/documentsui/TestActivity.java | 12 +++++++ .../documentsui/testing/TestResolveInfo.java | 34 +++++++++++++++++++ .../dirlist/DirectoryAddonsAdapterTest.java | 7 ++++ .../ModelBackedDocumentsAdapterTest.java | 8 +++++ .../documentsui/files/ActionHandlerTest.java | 33 ++++++++++++++++++ .../documentsui/picker/ActionHandlerTest.java | 34 +++++++++++++++++++ .../documentsui/picker/TestActivity.java | 8 +++++ 19 files changed, 265 insertions(+), 43 deletions(-) create mode 100644 tests/common/com/android/documentsui/AuthenticationActivity.java create mode 100644 tests/common/com/android/documentsui/testing/TestResolveInfo.java diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java index 67db9ee9a..b2bccd3ca 100644 --- a/src/com/android/documentsui/AbstractActionHandler.java +++ b/src/com/android/documentsui/AbstractActionHandler.java @@ -22,8 +22,10 @@ import static com.android.documentsui.base.Shared.DEBUG; import android.app.Activity; import android.app.LoaderManager.LoaderCallbacks; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.IntentSender; import android.content.Loader; import android.content.pm.ResolveInfo; import android.database.Cursor; @@ -76,6 +78,10 @@ import javax.annotation.Nullable; public abstract class AbstractActionHandler implements ActionHandler { + @VisibleForTesting + public static final int CODE_FORWARD = 42; + public static final int CODE_AUTHENTICATION = 43; + @VisibleForTesting static final int LOADER_ID = 42; @@ -147,6 +153,32 @@ public abstract class AbstractActionHandler listener).executeOnExecutor(ProviderExecutor.forAuthority(root.authority)); } + @Override + public void startAuthentication(PendingIntent intent) { + try { + mActivity.startIntentSenderForResult(intent.getIntentSender(), CODE_AUTHENTICATION, + null, 0, 0, 0); + } catch (IntentSender.SendIntentException cancelled) { + Log.d(TAG, "Authentication Pending Intent either canceled or ignored."); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case CODE_AUTHENTICATION: + onAuthenticationResult(resultCode); + break; + } + } + + private void onAuthenticationResult(int resultCode) { + if (resultCode == Activity.RESULT_OK) { + Log.v(TAG, "Authentication was successful. Refreshing directory now."); + mActivity.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE); + } + } + @Override public void getRootDocument(RootInfo root, int timeout, Consumer callback) { GetRootDocumentTask task = new GetRootDocumentTask( diff --git a/src/com/android/documentsui/ActionHandler.java b/src/com/android/documentsui/ActionHandler.java index cb6660226..e4abf7d0b 100644 --- a/src/com/android/documentsui/ActionHandler.java +++ b/src/com/android/documentsui/ActionHandler.java @@ -17,6 +17,7 @@ package com.android.documentsui; import android.annotation.IntDef; +import android.app.PendingIntent; import android.content.ContentProvider; import android.content.Intent; import android.content.pm.ResolveInfo; @@ -48,6 +49,8 @@ public interface ActionHandler { public static final int VIEW_TYPE_REGULAR = 1; public static final int VIEW_TYPE_PREVIEW = 2; + void onActivityResult(int requestCode, int resultCode, Intent data); + void openSettings(RootInfo root); /** @@ -73,6 +76,13 @@ public interface ActionHandler { */ void refreshDocument(DocumentInfo doc, BooleanConsumer callback); + + /** + * Attempts to start the authentication process caused by + * {@link android.app.AuthenticationRequiredException}. + */ + void startAuthentication(PendingIntent intent); + void showAppDetails(ResolveInfo info); void openRoot(RootInfo root); diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java index 929bc48ac..c89079e3b 100644 --- a/src/com/android/documentsui/BaseActivity.java +++ b/src/com/android/documentsui/BaseActivity.java @@ -574,6 +574,11 @@ public abstract class BaseActivity return super.dispatchKeyEvent(event); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + mInjector.actions.onActivityResult(requestCode, resultCode, data); + } + /** * Pops the top entry off the directory stack, and returns the user to the previous directory. * If the directory stack only contains one item, this method does nothing. diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java index adc925545..e10cdbac3 100644 --- a/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -1175,5 +1175,10 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) { setupDragAndDropOnDocumentView(holder.itemView, cursor); } + + @Override + public ActionHandler getActionHandler() { + return mActions; + } } } diff --git a/src/com/android/documentsui/dirlist/DocumentsAdapter.java b/src/com/android/documentsui/dirlist/DocumentsAdapter.java index d1f0e4ab2..82f2524a7 100644 --- a/src/com/android/documentsui/dirlist/DocumentsAdapter.java +++ b/src/com/android/documentsui/dirlist/DocumentsAdapter.java @@ -24,6 +24,7 @@ import android.provider.DocumentsContract.Document; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; +import com.android.documentsui.ActionHandler; import com.android.documentsui.Model; import com.android.documentsui.base.EventListener; import com.android.documentsui.base.Features; @@ -105,6 +106,7 @@ public abstract class DocumentsAdapter interface Environment { Context getContext(); Features getFeatures(); + ActionHandler getActionHandler(); int getColumnCount(); State getDisplayState(); boolean isInSearchMode(); diff --git a/src/com/android/documentsui/dirlist/Message.java b/src/com/android/documentsui/dirlist/Message.java index b9c67c68f..c90b12efd 100644 --- a/src/com/android/documentsui/dirlist/Message.java +++ b/src/com/android/documentsui/dirlist/Message.java @@ -129,11 +129,7 @@ abstract class Message { mCallback = () -> { AuthenticationRequiredException exception = (AuthenticationRequiredException) event.getException(); - try { - exception.getUserAction().send(); - } catch (PendingIntent.CanceledException ignored) { - Log.d(TAG, "User Action either caneled or ignored."); - } + mEnv.getActionHandler().startAuthentication(exception.getUserAction()); }; } } diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java index b00becfe6..7e740b18c 100644 --- a/src/com/android/documentsui/picker/ActionHandler.java +++ b/src/com/android/documentsui/picker/ActionHandler.java @@ -26,6 +26,7 @@ import static com.android.documentsui.base.State.ACTION_PICK_COPY_DESTINATION; import android.app.Activity; import android.app.FragmentManager; import android.content.ClipData; +import android.content.ComponentName; import android.content.Intent; import android.content.pm.ResolveInfo; import android.net.Uri; @@ -189,6 +190,32 @@ class ActionHandler extends AbstractActionHandler> mInjector; private SharedInputHandler mSharedInputHandler; @@ -200,34 +195,6 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { } } - @Override - public void onAppPicked(ResolveInfo info) { - final Intent intent = new Intent(getIntent()); - intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT); - intent.setComponent(new ComponentName( - info.activityInfo.applicationInfo.packageName, info.activityInfo.name)); - startActivityForResult(intent, CODE_FORWARD); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (DEBUG) Log.d(TAG, "onActivityResult() code=" + resultCode); - - // Only relay back results when not canceled; otherwise stick around to - // let the user pick another app/backend. - if (requestCode == CODE_FORWARD && resultCode != RESULT_CANCELED) { - - // Remember that we last picked via external app - mLastAccessed.setLastAccessedToExternalApp(this); - - // Pass back result to original caller - setResult(resultCode, data); - finish(); - } else { - super.onActivityResult(requestCode, resultCode, data); - } - } - @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); diff --git a/src/com/android/documentsui/services/ResolvedResourcesJob.java b/src/com/android/documentsui/services/ResolvedResourcesJob.java index 1632121e8..182a9ed35 100644 --- a/src/com/android/documentsui/services/ResolvedResourcesJob.java +++ b/src/com/android/documentsui/services/ResolvedResourcesJob.java @@ -16,7 +16,6 @@ package com.android.documentsui.services; -import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.net.Uri; diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml index 6a73f3939..0dc66a671 100644 --- a/tests/AndroidManifest.xml +++ b/tests/AndroidManifest.xml @@ -17,6 +17,13 @@ + + + + + + + startActivity; public TestEventListener startService; + public TestEventListener> startIntentSender; public TestEventListener rootPicked; public TestEventListener refreshCurrentRootAndDirectory; public TestEventListener setRootsDrawerOpen; @@ -77,6 +81,7 @@ public abstract class TestActivity extends AbstractBase { startActivity = new TestEventListener<>(); startService = new TestEventListener<>(); + startIntentSender = new TestEventListener<>(); rootPicked = new TestEventListener<>(); refreshCurrentRootAndDirectory = new TestEventListener<>(); setRootsDrawerOpen = new TestEventListener<>(); @@ -125,6 +130,13 @@ public abstract class TestActivity extends AbstractBase { return packageMgr; } + @Override + public final void startIntentSenderForResult(IntentSender intent, int requestCode, + @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) + throws IntentSender.SendIntentException { + startIntentSender.accept(new Pair<>(intent, requestCode)); + } + @Override public final void onRootPicked(RootInfo root) { rootPicked.accept(root); diff --git a/tests/common/com/android/documentsui/testing/TestResolveInfo.java b/tests/common/com/android/documentsui/testing/TestResolveInfo.java new file mode 100644 index 000000000..89db0b6b1 --- /dev/null +++ b/tests/common/com/android/documentsui/testing/TestResolveInfo.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.documentsui.testing; + +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; + +public class TestResolveInfo { + + public static ResolveInfo create() { + ResolveInfo info = new ResolveInfo(); + ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.name = "DocsUiTestActivity"; + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.packageName = "com.documentsui.test"; + activityInfo.applicationInfo = appInfo; + info.activityInfo = activityInfo; + return info; + } +} diff --git a/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java b/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java index b61fea154..4ce5e21d8 100644 --- a/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java +++ b/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java @@ -25,9 +25,11 @@ import android.support.v7.widget.RecyclerView; import android.test.AndroidTestCase; import android.view.ViewGroup; +import com.android.documentsui.ActionHandler; import com.android.documentsui.Model; import com.android.documentsui.base.Features; import com.android.documentsui.base.State; +import com.android.documentsui.testing.TestActionHandler; import com.android.documentsui.testing.TestEnv; @MediumTest @@ -37,10 +39,12 @@ public class DirectoryAddonsAdapterTest extends AndroidTestCase { private TestEnv mEnv; private DirectoryAddonsAdapter mAdapter; + private ActionHandler mActionHandler; public void setUp() { mEnv = TestEnv.create(AUTHORITY); + mActionHandler = new TestActionHandler(); mEnv.clear(); final Context testContext = TestContext.createStorageTestContext(getContext(), AUTHORITY); @@ -136,6 +140,9 @@ public class DirectoryAddonsAdapterTest extends AndroidTestCase { return mEnv.features; } + @Override + public ActionHandler getActionHandler() { return mActionHandler; } + @Override public boolean isSelected(String id) { return false; diff --git a/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java b/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java index 03957325e..18c5ef945 100644 --- a/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java +++ b/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java @@ -16,14 +16,17 @@ package com.android.documentsui.dirlist; +import android.app.PendingIntent; import android.content.Context; import android.database.Cursor; import android.support.test.filters.MediumTest; import android.test.AndroidTestCase; +import com.android.documentsui.ActionHandler; import com.android.documentsui.Model; import com.android.documentsui.base.Features; import com.android.documentsui.base.State; +import com.android.documentsui.testing.TestActionHandler; import com.android.documentsui.testing.TestEnv; @MediumTest @@ -32,12 +35,14 @@ public class ModelBackedDocumentsAdapterTest extends AndroidTestCase { private static final String AUTHORITY = "test_authority"; private TestEnv mEnv; + private ActionHandler mActionHandler; private ModelBackedDocumentsAdapter mAdapter; public void setUp() { final Context testContext = TestContext.createStorageTestContext(getContext(), AUTHORITY); mEnv = TestEnv.create(AUTHORITY); + mActionHandler = new TestActionHandler(); DocumentsAdapter.Environment env = new TestEnvironment(testContext); @@ -59,6 +64,9 @@ public class ModelBackedDocumentsAdapterTest extends AndroidTestCase { return mEnv.features; } + @Override + public ActionHandler getActionHandler() { return mActionHandler; } + private TestEnvironment(Context testContext) { this.testContext = testContext; } diff --git a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java index 8752311f8..24d30bca3 100644 --- a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java @@ -27,6 +27,8 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import android.app.Activity; +import android.app.PendingIntent; import android.content.ClipData; import android.content.Intent; import android.net.Uri; @@ -34,10 +36,12 @@ import android.os.Parcelable; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Path; import android.support.test.filters.MediumTest; +import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.util.Pair; import android.view.DragEvent; +import com.android.documentsui.AbstractActionHandler; import com.android.documentsui.R; import com.android.documentsui.TestActionModeAddons; import com.android.documentsui.archives.ArchivesProvider; @@ -483,6 +487,35 @@ public class ActionHandlerTest { } } + @Test + public void testAuthentication() throws Exception { + PendingIntent intent = PendingIntent.getActivity( + InstrumentationRegistry.getInstrumentation().getTargetContext(), 0, new Intent(), + 0); + + mHandler.startAuthentication(intent); + assertEquals(intent.getIntentSender(), mActivity.startIntentSender.getLastValue().first); + assertEquals(AbstractActionHandler.CODE_AUTHENTICATION, + mActivity.startIntentSender.getLastValue().second.intValue()); + } + + @Test + public void testOnActivityResult_onOK() throws Exception { + mHandler.onActivityResult(AbstractActionHandler.CODE_AUTHENTICATION, Activity.RESULT_OK, + null); + mActivity.refreshCurrentRootAndDirectory.assertCalled(); + } + + @Test + public void testOnActivityResult_onNotOK() throws Exception { + mHandler.onActivityResult(0, Activity.RESULT_OK, null); + mActivity.refreshCurrentRootAndDirectory.assertNotCalled(); + + mHandler.onActivityResult(AbstractActionHandler.CODE_AUTHENTICATION, + Activity.RESULT_CANCELED, null); + mActivity.refreshCurrentRootAndDirectory.assertNotCalled(); + } + private void assertRootPicked(Uri expectedUri) throws Exception { mEnv.beforeAsserts(); diff --git a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java index 56b437433..d559e6c1f 100644 --- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java @@ -31,6 +31,7 @@ import android.provider.DocumentsContract.Path; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; +import com.android.documentsui.AbstractActionHandler; import com.android.documentsui.R; import com.android.documentsui.base.DocumentStack; import com.android.documentsui.base.RootInfo; @@ -41,6 +42,7 @@ import com.android.documentsui.testing.DocumentStackAsserts; import com.android.documentsui.testing.TestEnv; import com.android.documentsui.testing.TestProvidersAccess; import com.android.documentsui.testing.TestLastAccessedStorage; +import com.android.documentsui.testing.TestResolveInfo; import org.junit.AfterClass; import org.junit.Before; @@ -410,6 +412,38 @@ public class ActionHandlerTest { mActivity.finishedHandler.assertCalled(); } + @Test + public void testOnAppPickedResult_OnOK() throws Exception { + Intent intent = new Intent(); + mHandler.onActivityResult(AbstractActionHandler.CODE_FORWARD, Activity.RESULT_OK, intent); + mActivity.finishedHandler.assertCalled(); + mActivity.setResult.assertCalled(); + + assertEquals(Activity.RESULT_OK, (long) mActivity.setResult.getLastValue().first); + assertEquals(intent, mActivity.setResult.getLastValue().second); + } + + @Test + public void testOnAppPickedResult_OnNotOK() throws Exception { + Intent intent = new Intent(); + mHandler.onActivityResult(0, Activity.RESULT_OK, intent); + mActivity.finishedHandler.assertNotCalled(); + mActivity.setResult.assertNotCalled(); + + mHandler.onActivityResult(AbstractActionHandler.CODE_FORWARD, Activity.RESULT_CANCELED, + intent); + mActivity.finishedHandler.assertNotCalled(); + mActivity.setResult.assertNotCalled(); + } + + @Test + public void testOpenAppRoot() throws Exception { + mHandler.openRoot(TestResolveInfo.create()); + assertEquals((long) mActivity.startActivityForResult.getLastValue().second, + AbstractActionHandler.CODE_FORWARD); + assertNotNull(mActivity.startActivityForResult.getLastValue().first); + } + private void testInitLocationDefaultToRecentsOnAction(@ActionType int action) throws Exception { mEnv.state.action = action; diff --git a/tests/unit/com/android/documentsui/picker/TestActivity.java b/tests/unit/com/android/documentsui/picker/TestActivity.java index 8ce91b80e..2a7b6be60 100644 --- a/tests/unit/com/android/documentsui/picker/TestActivity.java +++ b/tests/unit/com/android/documentsui/picker/TestActivity.java @@ -16,6 +16,7 @@ package com.android.documentsui.picker; +import android.annotation.RequiresPermission; import android.content.Intent; import android.util.Pair; @@ -27,6 +28,7 @@ import org.mockito.Mockito; public abstract class TestActivity extends AbstractBase { public TestEventListener> setResult; + public TestEventListener> startActivityForResult; public static TestActivity create(TestEnv env) { TestActivity activity = Mockito.mock(TestActivity.class, Mockito.CALLS_REAL_METHODS); @@ -39,12 +41,18 @@ public abstract class TestActivity extends AbstractBase { super.init(env); setResult = new TestEventListener<>(); + startActivityForResult = new TestEventListener<>(); } @Override public void setResult(int resultCode, Intent intent, int notUsed) { setResult.accept(Pair.create(resultCode, intent)); } + + @Override + public final void startActivityForResult(@RequiresPermission Intent intent, int requestCode) { + startActivityForResult.accept(Pair.create(intent, requestCode)); + } } // Trick Mockito into finding our Addons methods correctly. W/o this -- GitLab