Merge "Revert "Fix 'No Apps' UI issues of ManageApplications"" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
37b1831d0d
@@ -14,7 +14,7 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:settings="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
@@ -24,39 +24,40 @@
|
||||
android:id="@+id/pinned_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="2dp"
|
||||
settings:layout_constraintTop_toTopOf="parent"/>
|
||||
android:elevation="2dp"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/apps_list"
|
||||
<FrameLayout
|
||||
android:id="@+id/list_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:scrollbars="none"
|
||||
android:visibility="invisible"
|
||||
settings:fastScrollEnabled="true"
|
||||
settings:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
||||
settings:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
|
||||
settings:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
||||
settings:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
|
||||
settings:layout_constraintTop_toBottomOf="@id/pinned_header"
|
||||
settings:layout_constraintBottom_toBottomOf="parent"/>
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/no_applications"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:visibility="invisible"
|
||||
settings:layout_constraintTop_toTopOf="parent"
|
||||
settings:layout_constraintBottom_toBottomOf="parent"/>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/apps_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:scrollbars="none"
|
||||
settings:fastScrollEnabled="true"
|
||||
settings:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
||||
settings:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
|
||||
settings:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
||||
settings:fastScrollVerticalTrackDrawable="@drawable/line_drawable"/>
|
||||
|
||||
<include layout="@layout/loading_container"
|
||||
settings:layout_constraintTop_toBottomOf="@id/pinned_header"
|
||||
settings:layout_constraintBottom_toBottomOf="parent"/>
|
||||
<TextView
|
||||
android:id="@android:id/empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="bottom|center_horizontal"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/no_applications"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<include layout="@layout/loading_container"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
|
@@ -149,6 +149,8 @@
|
||||
<dimen name="wifi_assistant_height">182dp</dimen>
|
||||
<dimen name="wifi_assistant_image_top">32dp</dimen>
|
||||
<dimen name="wifi_assistant_image_start">24dp</dimen>
|
||||
<!-- appbar height is equal search bar height (48dp) plus search bar top and bottom margin -->
|
||||
<dimen name="app_bar_height">80dp</dimen>
|
||||
|
||||
<!-- CryptKeeper top margin for password/pin screen -->
|
||||
<dimen name="crypt_keeper_password_top_margin">88dip</dimen>
|
||||
|
@@ -72,11 +72,7 @@ public class RunningServices extends SettingsPreferenceFragment {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
boolean haveData = mRunningProcessesView.doResume(this, mRunningProcessesAvail);
|
||||
if (haveData) {
|
||||
mLoadingViewController.showContent(false /* animate */);
|
||||
} else {
|
||||
mLoadingViewController.showLoadingView();
|
||||
}
|
||||
mLoadingViewController.handleLoadingContainer(haveData /* done */, false /* animate */);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -208,6 +208,7 @@ public class ManageApplications extends InstrumentedFragment
|
||||
private ApplicationsAdapter mApplications;
|
||||
|
||||
private View mLoadingContainer;
|
||||
private View mListContainer;
|
||||
private SearchView mSearchView;
|
||||
|
||||
// Size resource used for packages whose size computation failed for some reason
|
||||
@@ -401,21 +402,25 @@ public class ManageApplications extends InstrumentedFragment
|
||||
|
||||
mRootView = inflater.inflate(R.layout.manage_applications_apps, null);
|
||||
mLoadingContainer = mRootView.findViewById(R.id.loading_container);
|
||||
mEmptyView = mRootView.findViewById(android.R.id.empty);
|
||||
mRecyclerView = mRootView.findViewById(R.id.apps_list);
|
||||
mListContainer = mRootView.findViewById(R.id.list_container);
|
||||
if (mListContainer != null) {
|
||||
// Create adapter and list view here
|
||||
mEmptyView = mListContainer.findViewById(android.R.id.empty);
|
||||
|
||||
mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter,
|
||||
savedInstanceState);
|
||||
if (savedInstanceState != null) {
|
||||
mApplications.mHasReceivedLoadEntries =
|
||||
savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false);
|
||||
mApplications.mHasReceivedBridgeCallback =
|
||||
savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false);
|
||||
mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter,
|
||||
savedInstanceState);
|
||||
if (savedInstanceState != null) {
|
||||
mApplications.mHasReceivedLoadEntries =
|
||||
savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false);
|
||||
mApplications.mHasReceivedBridgeCallback =
|
||||
savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false);
|
||||
}
|
||||
mRecyclerView = mListContainer.findViewById(R.id.apps_list);
|
||||
mRecyclerView.setItemAnimator(null);
|
||||
mRecyclerView.setLayoutManager(new LinearLayoutManager(
|
||||
getContext(), RecyclerView.VERTICAL, false /* reverseLayout */));
|
||||
mRecyclerView.setAdapter(mApplications);
|
||||
}
|
||||
mRecyclerView.setItemAnimator(null);
|
||||
mRecyclerView.setLayoutManager(new LinearLayoutManager(
|
||||
getContext(), RecyclerView.VERTICAL, false /* reverseLayout */));
|
||||
mRecyclerView.setAdapter(mApplications);
|
||||
|
||||
// We have to do this now because PreferenceFrameLayout looks at it
|
||||
// only when the view is added.
|
||||
@@ -980,8 +985,16 @@ public class ManageApplications extends InstrumentedFragment
|
||||
// overlapped by floating filter.
|
||||
if (hasFilter) {
|
||||
mManageApplications.mSpinnerHeader.setVisibility(View.VISIBLE);
|
||||
mManageApplications.mRecyclerView.setPadding(0 /* left */,
|
||||
mContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.app_bar_height) /* top */,
|
||||
0 /* right */,
|
||||
0 /* bottom */);
|
||||
} else {
|
||||
mManageApplications.mSpinnerHeader.setVisibility(View.GONE);
|
||||
mManageApplications.mRecyclerView.setPadding(0 /* left */, 0 /* top */,
|
||||
0 /* right */,
|
||||
0 /* bottom */);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1031,8 +1044,7 @@ public class ManageApplications extends InstrumentedFragment
|
||||
mManageApplications = manageApplications;
|
||||
mLoadingViewController = new LoadingViewController(
|
||||
mManageApplications.mLoadingContainer,
|
||||
mManageApplications.mRecyclerView,
|
||||
mManageApplications.mEmptyView
|
||||
mManageApplications.mListContainer
|
||||
);
|
||||
mContext = manageApplications.getActivity();
|
||||
mIconDrawableFactory = IconDrawableFactory.newInstance(mContext);
|
||||
@@ -1291,9 +1303,11 @@ public class ManageApplications extends InstrumentedFragment
|
||||
mOriginalEntries = entries;
|
||||
notifyDataSetChanged();
|
||||
if (getItemCount() == 0) {
|
||||
mLoadingViewController.showEmpty(false /* animate */);
|
||||
mManageApplications.mRecyclerView.setVisibility(View.GONE);
|
||||
mManageApplications.mEmptyView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mLoadingViewController.showContent(false /* animate */);
|
||||
mManageApplications.mEmptyView.setVisibility(View.GONE);
|
||||
mManageApplications.mRecyclerView.setVisibility(View.VISIBLE);
|
||||
|
||||
if (mManageApplications.mSearchView != null
|
||||
&& mManageApplications.mSearchView.isVisibleToUser()) {
|
||||
@@ -1310,6 +1324,10 @@ public class ManageApplications extends InstrumentedFragment
|
||||
mLastIndex = -1;
|
||||
}
|
||||
|
||||
if (mSession.getAllApps().size() != 0
|
||||
&& mManageApplications.mListContainer.getVisibility() != View.VISIBLE) {
|
||||
mLoadingViewController.showContent(true /* animate */);
|
||||
}
|
||||
if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
|
||||
// No enabled or disabled filters for usage access.
|
||||
return;
|
||||
|
@@ -22,66 +22,34 @@ import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A helper class that manages show/hide loading spinner, content view and empty view (optional).
|
||||
* A helper class that manages show/hide loading spinner.
|
||||
*/
|
||||
public class LoadingViewController {
|
||||
|
||||
private static final long DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS = 100L;
|
||||
|
||||
private final Handler mFgHandler;
|
||||
private final View mLoadingView;
|
||||
private final View mContentView;
|
||||
private final View mEmptyView;
|
||||
public final Handler mFgHandler;
|
||||
public final View mLoadingView;
|
||||
public final View mContentView;
|
||||
|
||||
public LoadingViewController(View loadingView, View contentView) {
|
||||
this(loadingView, contentView, null /* emptyView*/);
|
||||
}
|
||||
|
||||
public LoadingViewController(View loadingView, View contentView, @Nullable View emptyView) {
|
||||
mLoadingView = loadingView;
|
||||
mContentView = contentView;
|
||||
mEmptyView = emptyView;
|
||||
mFgHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
private Runnable mShowLoadingContainerRunnable = new Runnable() {
|
||||
public void run() {
|
||||
showLoadingView();
|
||||
handleLoadingContainer(false /* done */, false /* animate */);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows content view and hides loading view & empty view.
|
||||
*/
|
||||
public void showContent(boolean animate) {
|
||||
// Cancel any pending task to show the loading animation and show the list of
|
||||
// apps directly.
|
||||
mFgHandler.removeCallbacks(mShowLoadingContainerRunnable);
|
||||
handleLoadingContainer(true /* showContent */, false /* showEmpty*/, animate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows empty view and hides loading view & content view.
|
||||
*/
|
||||
public void showEmpty(boolean animate) {
|
||||
if (mEmptyView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel any pending task to show the loading animation and show the list of
|
||||
// apps directly.
|
||||
mFgHandler.removeCallbacks(mShowLoadingContainerRunnable);
|
||||
handleLoadingContainer(false /* showContent */, true /* showEmpty */, animate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows loading view and hides content view & empty view.
|
||||
*/
|
||||
public void showLoadingView() {
|
||||
handleLoadingContainer(false /* showContent */, false /* showEmpty */, false /* animate */);
|
||||
handleLoadingContainer(true /* show */, animate);
|
||||
}
|
||||
|
||||
public void showLoadingViewDelayed() {
|
||||
@@ -89,9 +57,8 @@ public class LoadingViewController {
|
||||
mShowLoadingContainerRunnable, DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS);
|
||||
}
|
||||
|
||||
private void handleLoadingContainer(boolean showContent, boolean showEmpty, boolean animate) {
|
||||
handleLoadingContainer(mLoadingView, mContentView, mEmptyView,
|
||||
showContent, showEmpty, animate);
|
||||
public void handleLoadingContainer(boolean done, boolean animate) {
|
||||
handleLoadingContainer(mLoadingView, mContentView, done, animate);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,25 +75,6 @@ public class LoadingViewController {
|
||||
setViewShown(content, done, animate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show/hide loading view and content view and empty view.
|
||||
*
|
||||
* @param loading The loading spinner view
|
||||
* @param content The content view
|
||||
* @param empty The empty view shows no item summary to users.
|
||||
* @param showContent If true, content is set visible and loading is set invisible.
|
||||
* @param showEmpty If true, empty is set visible and loading is set invisible.
|
||||
* @param animate Whether or not content/loading views should animate in/out.
|
||||
*/
|
||||
public static void handleLoadingContainer(View loading, View content, View empty,
|
||||
boolean showContent, boolean showEmpty, boolean animate) {
|
||||
if (empty != null) {
|
||||
setViewShown(empty, showEmpty, animate);
|
||||
}
|
||||
setViewShown(content, showContent, animate);
|
||||
setViewShown(loading, !showContent && !showEmpty, animate);
|
||||
}
|
||||
|
||||
private static void setViewShown(final View view, boolean shown, boolean animate) {
|
||||
if (animate) {
|
||||
Animation animation = AnimationUtils.loadAnimation(view.getContext(),
|
||||
|
@@ -28,6 +28,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -48,6 +49,7 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SearchView;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
@@ -153,6 +155,22 @@ public class ManageApplicationsTest {
|
||||
assertThat(mMenu.findItem(R.id.sort_order_frequent_notification).isVisible()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onCreateView_shouldNotShowLoadingContainer() {
|
||||
ReflectionHelpers.setField(mFragment, "mResetAppsHelper", mock(ResetAppsHelper.class));
|
||||
doNothing().when(mFragment).createHeader();
|
||||
|
||||
final LayoutInflater layoutInflater = mock(LayoutInflater.class);
|
||||
final View view = mock(View.class);
|
||||
final View loadingContainer = mock(View.class);
|
||||
when(layoutInflater.inflate(anyInt(), eq(null))).thenReturn(view);
|
||||
when(view.findViewById(R.id.loading_container)).thenReturn(loadingContainer);
|
||||
|
||||
mFragment.onCreateView(layoutInflater, mock(ViewGroup.class), null);
|
||||
|
||||
verify(loadingContainer, never()).setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onCreateOptionsMenu_shouldSetSearchQueryListener() {
|
||||
final SearchView searchView = mock(SearchView.class);
|
||||
@@ -203,6 +221,7 @@ public class ManageApplicationsTest {
|
||||
@Test
|
||||
public void updateLoading_appLoaded_shouldNotDelayCallToHandleLoadingContainer() {
|
||||
ReflectionHelpers.setField(mFragment, "mLoadingContainer", mock(View.class));
|
||||
ReflectionHelpers.setField(mFragment, "mListContainer", mock(View.class));
|
||||
final ManageApplications.ApplicationsAdapter adapter =
|
||||
spy(new ManageApplications.ApplicationsAdapter(mState, mFragment,
|
||||
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL), new Bundle()));
|
||||
@@ -224,6 +243,7 @@ public class ManageApplicationsTest {
|
||||
@Test
|
||||
public void updateLoading_appNotLoaded_shouldDelayCallToHandleLoadingContainer() {
|
||||
ReflectionHelpers.setField(mFragment, "mLoadingContainer", mock(View.class));
|
||||
ReflectionHelpers.setField(mFragment, "mListContainer", mock(View.class));
|
||||
final ManageApplications.ApplicationsAdapter adapter =
|
||||
spy(new ManageApplications.ApplicationsAdapter(mState, mFragment,
|
||||
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL), new Bundle()));
|
||||
@@ -252,6 +272,7 @@ public class ManageApplicationsTest {
|
||||
when(listContainer.getVisibility()).thenReturn(View.INVISIBLE);
|
||||
when(listContainer.getContext()).thenReturn(context);
|
||||
ReflectionHelpers.setField(mFragment, "mLoadingContainer", loadingContainer);
|
||||
ReflectionHelpers.setField(mFragment, "mListContainer", listContainer);
|
||||
final ManageApplications.ApplicationsAdapter adapter =
|
||||
spy(new ManageApplications.ApplicationsAdapter(mState, mFragment,
|
||||
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL), new Bundle()));
|
||||
@@ -275,7 +296,7 @@ public class ManageApplicationsTest {
|
||||
|
||||
adapter.onRebuildComplete(null);
|
||||
|
||||
verify(loadingViewController).showEmpty(false /* animate */);
|
||||
verify(loadingViewController).showContent(true /* animate */);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -283,16 +304,15 @@ public class ManageApplicationsTest {
|
||||
final String query = "Test";
|
||||
final RecyclerView recyclerView = mock(RecyclerView.class);
|
||||
final View emptyView = mock(View.class);
|
||||
final View loadingContainer = mock(View.class);
|
||||
ReflectionHelpers.setField(mFragment, "mRecyclerView", recyclerView);
|
||||
ReflectionHelpers.setField(mFragment, "mEmptyView", emptyView);
|
||||
ReflectionHelpers.setField(mFragment, "mLoadingContainer", loadingContainer);
|
||||
final SearchView searchView = mock(SearchView.class);
|
||||
ReflectionHelpers.setField(mFragment, "mSearchView", searchView);
|
||||
when(searchView.isVisibleToUser()).thenReturn(true);
|
||||
when(searchView.getQuery()).thenReturn(query);
|
||||
final View listContainer = mock(View.class);
|
||||
when(listContainer.getVisibility()).thenReturn(View.VISIBLE);
|
||||
ReflectionHelpers.setField(mFragment, "mListContainer", listContainer);
|
||||
ReflectionHelpers.setField(
|
||||
mFragment, "mFilterAdapter", mock(ManageApplications.FilterSpinnerAdapter.class));
|
||||
final ArrayList<ApplicationsState.AppEntry> appList = new ArrayList<>();
|
||||
@@ -471,6 +491,8 @@ public class ManageApplicationsTest {
|
||||
mFragment.mFilterAdapter.updateFilterView(true);
|
||||
|
||||
assertThat(mFragment.mSpinnerHeader.getVisibility()).isEqualTo(View.VISIBLE);
|
||||
assertThat(mFragment.mRecyclerView.getPaddingTop()).isEqualTo(
|
||||
mContext.getResources().getDimensionPixelSize(R.dimen.app_bar_height));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Reference in New Issue
Block a user