diff options
| author | 2023-09-27 18:51:45 +0000 | |
|---|---|---|
| committer | 2023-09-29 16:45:24 +0000 | |
| commit | 61b4a7e489b3a13f0a012c8d338ed63a3d3cf505 (patch) | |
| tree | bde09e07328a24eb9d924398ac6ba34ee5d444a9 /java | |
| parent | faac60a597aa8f23f03a86feab76ad0f86854219 (diff) | |
Introduce EmptyStateUiHelper (& initial tests)
Based on prototype work in ag/24516421, this CL introduces a new
component to handle the empty-state UI implementation details that had
previously been implemented in `MultiProfilePagerAdapter`, since those
details significantly clutter the implementation of that adapter's
other responsibilities.
As in ag/24516421 patchset #4, this just sets up the boilerplate and
kicks off with some "low-hanging-fruit" operations. Follow-up CLs will
continue migrating these responsibilities as in ag/24516421 (except
with more incremental testing).
Bug: 302311217
Test: IntentResolverUnitTests, CtsSharesheetDeviceTest
Change-Id: Ie9bb7f4e97836321521c3cf13c77cafc97b1a461
Diffstat (limited to 'java')
4 files changed, 200 insertions, 35 deletions
diff --git a/java/src/com/android/intentresolver/MultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/MultiProfilePagerAdapter.java index 2c98d89f..8c640dd3 100644 --- a/java/src/com/android/intentresolver/MultiProfilePagerAdapter.java +++ b/java/src/com/android/intentresolver/MultiProfilePagerAdapter.java @@ -29,6 +29,7 @@ import androidx.viewpager.widget.ViewPager; import com.android.intentresolver.emptystate.EmptyState; import com.android.intentresolver.emptystate.EmptyStateProvider; +import com.android.intentresolver.emptystate.EmptyStateUiHelper; import com.android.internal.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; @@ -424,7 +425,7 @@ public class MultiProfilePagerAdapter< clickListener = v -> emptyState.getButtonClickListener().onClick(() -> { ProfileDescriptor<PageViewT, SinglePageAdapterT> descriptor = getItem( userHandleToPageIndex(listAdapter.getUserHandle())); - MultiProfilePagerAdapter.this.showSpinner(descriptor.getEmptyStateView()); + descriptor.mEmptyStateUi.showSpinner(); }); } @@ -451,9 +452,9 @@ public class MultiProfilePagerAdapter< userHandleToPageIndex(activeListAdapter.getUserHandle())); descriptor.mRootView.findViewById( com.android.internal.R.id.resolver_list).setVisibility(View.GONE); + descriptor.mEmptyStateUi.resetViewVisibilities(); + ViewGroup emptyStateView = descriptor.getEmptyStateView(); - resetViewVisibilitiesForEmptyState(emptyStateView); - emptyStateView.setVisibility(View.VISIBLE); View container = emptyStateView.findViewById( com.android.internal.R.id.resolver_empty_state_container); @@ -504,36 +505,12 @@ public class MultiProfilePagerAdapter< paddingBottom)); } - private void showSpinner(View emptyStateView) { - emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_title) - .setVisibility(View.INVISIBLE); - emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_button) - .setVisibility(View.INVISIBLE); - emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_progress) - .setVisibility(View.VISIBLE); - emptyStateView.findViewById(com.android.internal.R.id.empty).setVisibility(View.GONE); - } - - private void resetViewVisibilitiesForEmptyState(View emptyStateView) { - emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_title) - .setVisibility(View.VISIBLE); - emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_subtitle) - .setVisibility(View.VISIBLE); - emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_button) - .setVisibility(View.INVISIBLE); - emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_progress) - .setVisibility(View.GONE); - emptyStateView.findViewById(com.android.internal.R.id.empty).setVisibility(View.GONE); - } - protected void showListView(ListAdapterT activeListAdapter) { ProfileDescriptor<PageViewT, SinglePageAdapterT> descriptor = getItem( userHandleToPageIndex(activeListAdapter.getUserHandle())); descriptor.mRootView.findViewById( com.android.internal.R.id.resolver_list).setVisibility(View.VISIBLE); - View emptyStateView = descriptor.mRootView.findViewById( - com.android.internal.R.id.resolver_empty_state); - emptyStateView.setVisibility(View.GONE); + descriptor.mEmptyStateUi.hide(); } public boolean shouldShowEmptyStateScreen(ListAdapterT listAdapter) { @@ -547,6 +524,10 @@ public class MultiProfilePagerAdapter< // should be the owner of all per-profile data (especially now that the API is generic)? private static class ProfileDescriptor<PageViewT, SinglePageAdapterT> { final ViewGroup mRootView; + final EmptyStateUiHelper mEmptyStateUi; + + // TODO: post-refactoring, we may not need to retain these ivars directly (since they may + // be encapsulated within the `EmptyStateUiHelper`?). private final ViewGroup mEmptyStateView; private final SinglePageAdapterT mAdapter; @@ -557,6 +538,7 @@ public class MultiProfilePagerAdapter< mAdapter = adapter; mEmptyStateView = rootView.findViewById(com.android.internal.R.id.resolver_empty_state); mView = (PageViewT) rootView.findViewById(com.android.internal.R.id.resolver_list); + mEmptyStateUi = new EmptyStateUiHelper(rootView); } protected ViewGroup getEmptyStateView() { diff --git a/java/src/com/android/intentresolver/emptystate/EmptyStateUiHelper.java b/java/src/com/android/intentresolver/emptystate/EmptyStateUiHelper.java new file mode 100644 index 00000000..d7ef8c75 --- /dev/null +++ b/java/src/com/android/intentresolver/emptystate/EmptyStateUiHelper.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 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.intentresolver.emptystate; + +import android.view.View; +import android.view.ViewGroup; + +/** + * Helper for building `MultiProfilePagerAdapter` tab UIs for profile tabs that are "blocked" by + * some empty-state status. + */ +public class EmptyStateUiHelper { + private final View mEmptyStateView; + + public EmptyStateUiHelper(ViewGroup rootView) { + mEmptyStateView = + rootView.requireViewById(com.android.internal.R.id.resolver_empty_state); + } + + public void resetViewVisibilities() { + mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_title) + .setVisibility(View.VISIBLE); + mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_subtitle) + .setVisibility(View.VISIBLE); + mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_button) + .setVisibility(View.INVISIBLE); + mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_progress) + .setVisibility(View.GONE); + mEmptyStateView.requireViewById(com.android.internal.R.id.empty) + .setVisibility(View.GONE); + mEmptyStateView.setVisibility(View.VISIBLE); + } + + public void showSpinner() { + mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_title) + .setVisibility(View.INVISIBLE); + // TODO: subtitle? + mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_button) + .setVisibility(View.INVISIBLE); + mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_progress) + .setVisibility(View.VISIBLE); + mEmptyStateView.requireViewById(com.android.internal.R.id.empty) + .setVisibility(View.GONE); + } + + public void hide() { + mEmptyStateView.setVisibility(View.GONE); + } +} + diff --git a/java/tests/src/com/android/intentresolver/MultiProfilePagerAdapterTest.kt b/java/tests/src/com/android/intentresolver/MultiProfilePagerAdapterTest.kt index 56034f0a..ed06f7d1 100644 --- a/java/tests/src/com/android/intentresolver/MultiProfilePagerAdapterTest.kt +++ b/java/tests/src/com/android/intentresolver/MultiProfilePagerAdapterTest.kt @@ -17,7 +17,9 @@ package com.android.intentresolver import android.os.UserHandle +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import android.widget.ListView import androidx.test.platform.app.InstrumentationRegistry import com.android.intentresolver.MultiProfilePagerAdapter.PROFILE_PERSONAL @@ -26,6 +28,7 @@ import com.android.intentresolver.emptystate.EmptyStateProvider import com.google.common.collect.ImmutableList import com.google.common.truth.Truth.assertThat import java.util.Optional +import java.util.function.Supplier import org.junit.Test import org.mockito.Mockito.never import org.mockito.Mockito.verify @@ -35,6 +38,10 @@ class MultiProfilePagerAdapterTest { private val WORK_USER_HANDLE = UserHandle.of(20) private val context = InstrumentationRegistry.getInstrumentation().getContext() + private val inflater = Supplier { + LayoutInflater.from(context).inflate(R.layout.resolver_list_per_profile, null, false) + as ViewGroup + } @Test fun testSinglePageProfileAdapter() { @@ -52,7 +59,7 @@ class MultiProfilePagerAdapterTest { PROFILE_PERSONAL, null, null, - { ListView(context) }, + inflater, { Optional.empty() } ) assertThat(pagerAdapter.count).isEqualTo(1) @@ -86,7 +93,7 @@ class MultiProfilePagerAdapterTest { PROFILE_PERSONAL, WORK_USER_HANDLE, // TODO: why does this test pass even if this is null? null, - { ListView(context) }, + inflater, { Optional.empty() } ) assertThat(pagerAdapter.count).isEqualTo(2) @@ -125,7 +132,7 @@ class MultiProfilePagerAdapterTest { PROFILE_WORK, // <-- This test specifically requests we start on work profile. WORK_USER_HANDLE, // TODO: why does this test pass even if this is null? null, - { ListView(context) }, + inflater, { Optional.empty() } ) assertThat(pagerAdapter.count).isEqualTo(2) @@ -165,7 +172,7 @@ class MultiProfilePagerAdapterTest { PROFILE_PERSONAL, null, null, - { ListView(context) }, + inflater, { Optional.empty() } ) pagerAdapter.setupContainerPadding(container) @@ -193,7 +200,7 @@ class MultiProfilePagerAdapterTest { PROFILE_PERSONAL, null, null, - { ListView(context) }, + inflater, { Optional.of(42) } ) pagerAdapter.setupContainerPadding(container) @@ -227,7 +234,7 @@ class MultiProfilePagerAdapterTest { PROFILE_WORK, WORK_USER_HANDLE, null, - { ListView(context) }, + inflater, { Optional.empty() } ) assertThat(pagerAdapter.shouldShowEmptyStateScreen(workListAdapter)).isTrue() @@ -261,7 +268,7 @@ class MultiProfilePagerAdapterTest { PROFILE_WORK, WORK_USER_HANDLE, null, - { ListView(context) }, + inflater, { Optional.empty() } ) assertThat(pagerAdapter.shouldShowEmptyStateScreen(workListAdapter)).isFalse() diff --git a/java/tests/src/com/android/intentresolver/emptystate/EmptyStateUiHelperTest.kt b/java/tests/src/com/android/intentresolver/emptystate/EmptyStateUiHelperTest.kt new file mode 100644 index 00000000..bc5545db --- /dev/null +++ b/java/tests/src/com/android/intentresolver/emptystate/EmptyStateUiHelperTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2023 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.intentresolver.emptystate + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test + +class EmptyStateUiHelperTest { + private val context = InstrumentationRegistry.getInstrumentation().getContext() + + lateinit var rootContainer: ViewGroup + lateinit var emptyStateTitleView: View + lateinit var emptyStateSubtitleView: View + lateinit var emptyStateButtonView: View + lateinit var emptyStateProgressView: View + lateinit var emptyStateDefaultTextView: View + lateinit var emptyStateContainerView: View + lateinit var emptyStateRootView: View + lateinit var emptyStateUiHelper: EmptyStateUiHelper + + @Before + fun setup() { + rootContainer = FrameLayout(context) + LayoutInflater.from(context) + .inflate( + com.android.intentresolver.R.layout.resolver_list_per_profile, + rootContainer, + true + ) + emptyStateRootView = + rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state) + emptyStateTitleView = + rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_title) + emptyStateSubtitleView = rootContainer.requireViewById( + com.android.internal.R.id.resolver_empty_state_subtitle) + emptyStateButtonView = rootContainer.requireViewById( + com.android.internal.R.id.resolver_empty_state_button) + emptyStateProgressView = rootContainer.requireViewById( + com.android.internal.R.id.resolver_empty_state_progress) + emptyStateDefaultTextView = + rootContainer.requireViewById(com.android.internal.R.id.empty) + emptyStateContainerView = rootContainer.requireViewById( + com.android.internal.R.id.resolver_empty_state_container) + emptyStateUiHelper = EmptyStateUiHelper(rootContainer) + } + + @Test + fun testResetViewVisibilities() { + // First set each view's visibility to differ from the expected "reset" state so we can then + // assert that they're all reset afterward. + // TODO: for historic reasons "reset" doesn't cover `emptyStateContainerView`; should it? + emptyStateRootView.visibility = View.GONE + emptyStateTitleView.visibility = View.GONE + emptyStateSubtitleView.visibility = View.GONE + emptyStateButtonView.visibility = View.VISIBLE + emptyStateProgressView.visibility = View.VISIBLE + emptyStateDefaultTextView.visibility = View.VISIBLE + + emptyStateUiHelper.resetViewVisibilities() + + assertThat(emptyStateRootView.visibility).isEqualTo(View.VISIBLE) + assertThat(emptyStateTitleView.visibility).isEqualTo(View.VISIBLE) + assertThat(emptyStateSubtitleView.visibility).isEqualTo(View.VISIBLE) + assertThat(emptyStateButtonView.visibility).isEqualTo(View.INVISIBLE) + assertThat(emptyStateProgressView.visibility).isEqualTo(View.GONE) + assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE) + } + + @Test + fun testShowSpinner() { + emptyStateTitleView.visibility = View.VISIBLE + emptyStateButtonView.visibility = View.VISIBLE + emptyStateProgressView.visibility = View.GONE + emptyStateDefaultTextView.visibility = View.VISIBLE + + emptyStateUiHelper.showSpinner() + + // TODO: should this cover any other views? Subtitle? + assertThat(emptyStateTitleView.visibility).isEqualTo(View.INVISIBLE) + assertThat(emptyStateButtonView.visibility).isEqualTo(View.INVISIBLE) + assertThat(emptyStateProgressView.visibility).isEqualTo(View.VISIBLE) + assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE) + } + + @Test + fun testHide() { + emptyStateRootView.visibility = View.VISIBLE + + emptyStateUiHelper.hide() + + assertThat(emptyStateRootView.visibility).isEqualTo(View.GONE) + } +} |