Merge "Delay showing the loading progress spinner in Managed App." into oc-dr1-dev am: 153e2b89b9

am: 33b2f7f466

Change-Id: I601744831f54fc2bd86a6f2c692e38ba361b3603
This commit is contained in:
Doris Ling
2017-06-21 03:35:04 +00:00
committed by android-build-merger
3 changed files with 144 additions and 7 deletions

View File

@@ -943,6 +943,8 @@ public final class Utils extends com.android.settingslib.Utils {
return result; return result;
} }
// TODO: move this out of Utils to a mixin or a controller or a helper class.
@Deprecated
public static void handleLoadingContainer(View loading, View doneLoading, boolean done, public static void handleLoadingContainer(View loading, View doneLoading, boolean done,
boolean animate) { boolean animate) {
setViewShown(loading, !done, animate); setViewShown(loading, !done, animate);

View File

@@ -346,7 +346,6 @@ public class ManageApplications extends InstrumentedPreferenceFragment
mRootView = inflater.inflate(R.layout.manage_applications_apps, null); mRootView = inflater.inflate(R.layout.manage_applications_apps, null);
mLoadingContainer = mRootView.findViewById(R.id.loading_container); mLoadingContainer = mRootView.findViewById(R.id.loading_container);
mLoadingContainer.setVisibility(View.VISIBLE);
mListContainer = mRootView.findViewById(R.id.list_container); mListContainer = mRootView.findViewById(R.id.list_container);
if (mListContainer != null) { if (mListContainer != null) {
// Create adapter and list view here // Create adapter and list view here
@@ -395,7 +394,8 @@ public class ManageApplications extends InstrumentedPreferenceFragment
return mRootView; return mRootView;
} }
private void createHeader() { @VisibleForTesting
void createHeader() {
Activity activity = getActivity(); Activity activity = getActivity();
FrameLayout pinnedHeader = (FrameLayout) mRootView.findViewById(R.id.pinned_header); FrameLayout pinnedHeader = (FrameLayout) mRootView.findViewById(R.id.pinned_header);
mSpinnerHeader = activity.getLayoutInflater() mSpinnerHeader = activity.getLayoutInflater()
@@ -834,6 +834,10 @@ public class ManageApplications extends InstrumentedPreferenceFragment
static class ApplicationsAdapter extends BaseAdapter implements Filterable, static class ApplicationsAdapter extends BaseAdapter implements Filterable,
ApplicationsState.Callbacks, AppStateBaseBridge.Callback, ApplicationsState.Callbacks, AppStateBaseBridge.Callback,
AbsListView.RecyclerListener, SectionIndexer { AbsListView.RecyclerListener, SectionIndexer {
// how long to wait for app list to populate without showing the loading container
private static final long DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS = 100L;
private static final SectionInfo[] EMPTY_SECTIONS = new SectionInfo[0]; private static final SectionInfo[] EMPTY_SECTIONS = new SectionInfo[0];
private final ApplicationsState mState; private final ApplicationsState mState;
@@ -889,6 +893,13 @@ public class ManageApplications extends InstrumentedPreferenceFragment
} }
}; };
private Runnable mShowLoadingContainerRunnable = new Runnable() {
public void run() {
Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
mManageApplications.mListContainer, false /* done */, false /* animate */);
}
};
public ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications, public ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications,
int filterMode) { int filterMode) {
mState = state; mState = state;
@@ -1097,6 +1108,9 @@ public class ManageApplications extends InstrumentedPreferenceFragment
if (mSession.getAllApps().size() != 0 if (mSession.getAllApps().size() != 0
&& mManageApplications.mListContainer.getVisibility() != View.VISIBLE) { && mManageApplications.mListContainer.getVisibility() != View.VISIBLE) {
// Cancel any pending task to show the loading animation and show the list of
// apps directly.
mFgHandler.removeCallbacks(mShowLoadingContainerRunnable);
Utils.handleLoadingContainer(mManageApplications.mLoadingContainer, Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
mManageApplications.mListContainer, true, true); mManageApplications.mListContainer, true, true);
} }
@@ -1148,10 +1162,16 @@ public class ManageApplications extends InstrumentedPreferenceFragment
} }
} }
private void updateLoading() { @VisibleForTesting
void updateLoading() {
final boolean appLoaded = mHasReceivedLoadEntries && mSession.getAllApps().size() != 0;
if (appLoaded) {
Utils.handleLoadingContainer(mManageApplications.mLoadingContainer, Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
mManageApplications.mListContainer, mManageApplications.mListContainer, true /* done */, false /* animate */);
mHasReceivedLoadEntries && mSession.getAllApps().size() != 0, false); } else {
mFgHandler.postDelayed(
mShowLoadingContainerRunnable, DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS);
}
} }
ArrayList<ApplicationsState.AppEntry> applyPrefixFilter(CharSequence prefix, ArrayList<ApplicationsState.AppEntry> applyPrefixFilter(CharSequence prefix,

View File

@@ -17,29 +17,38 @@
package com.android.settings.applications; package com.android.settings.applications;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.Settings; import com.android.settings.Settings;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources; import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.SettingsShadowResources.SettingsShadowTheme; import com.android.settings.testutils.shadow.SettingsShadowResources.SettingsShadowTheme;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor; import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import com.android.settings.testutils.shadow.ShadowEventLogWriter; import com.android.settings.testutils.shadow.ShadowEventLogWriter;
import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.Callbacks;
import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.Lifecycle;
import java.util.ArrayList;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.robolectric.fakes.RoboMenuItem; import org.robolectric.fakes.RoboMenuItem;
import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers;
@@ -47,7 +56,12 @@ import org.robolectric.util.ReflectionHelpers;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -132,6 +146,107 @@ public class ManageApplicationsTest {
assertThat(mMenu.findItem(R.id.reset_app_preferences).isVisible()).isFalse(); assertThat(mMenu.findItem(R.id.reset_app_preferences).isVisible()).isFalse();
} }
@Test
public void onCreateView_shouldNotShowLoadingContainer() {
final ManageApplications fragment = spy(new ManageApplications());
ReflectionHelpers.setField(fragment, "mResetAppsHelper",
mock(ResetAppsHelper.class));
doNothing().when(fragment).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);
fragment.onCreateView(layoutInflater, mock(ViewGroup.class), null);
verify(loadingContainer, never()).setVisibility(View.VISIBLE);
}
@Test
public void updateLoading_appLoaded_shouldNotDelayCallToHandleLoadingContainer() {
final ManageApplications fragment = mock(ManageApplications.class);
ReflectionHelpers.setField(fragment, "mLoadingContainer", mock(View.class));
ReflectionHelpers.setField(fragment, "mListContainer", mock(View.class));
when(fragment.getActivity()).thenReturn(mock(Activity.class));
final Runnable showLoadingContainerRunnable = mock(Runnable.class);
final Handler handler = mock(Handler.class);
final ManageApplications.ApplicationsAdapter adapter =
spy(new ManageApplications.ApplicationsAdapter(mState, fragment, 0));
ReflectionHelpers.setField(adapter, "mShowLoadingContainerRunnable",
showLoadingContainerRunnable);
ReflectionHelpers.setField(adapter, "mFgHandler", handler);
// app loading completed
ReflectionHelpers.setField(adapter, "mHasReceivedLoadEntries", true);
final ArrayList<ApplicationsState.AppEntry> appList = new ArrayList<>();
appList.add(mock(ApplicationsState.AppEntry.class));
when(mSession.getAllApps()).thenReturn(appList);
adapter.updateLoading();
verify(handler, never()).postDelayed(eq(showLoadingContainerRunnable), anyLong());
}
@Test
public void updateLoading_appNotLoaded_shouldDelayCallToHandleLoadingContainer() {
final ManageApplications fragment = mock(ManageApplications.class);
ReflectionHelpers.setField(fragment, "mLoadingContainer", mock(View.class));
ReflectionHelpers.setField(fragment, "mListContainer", mock(View.class));
when(fragment.getActivity()).thenReturn(mock(Activity.class));
final Runnable showLoadingContainerRunnable = mock(Runnable.class);
final Handler handler = mock(Handler.class);
final ManageApplications.ApplicationsAdapter adapter =
spy(new ManageApplications.ApplicationsAdapter(mState, fragment, 0));
ReflectionHelpers.setField(adapter, "mShowLoadingContainerRunnable",
showLoadingContainerRunnable);
ReflectionHelpers.setField(adapter, "mFgHandler", handler);
// app loading not yet completed
ReflectionHelpers.setField(adapter, "mHasReceivedLoadEntries", false);
adapter.updateLoading();
verify(handler).postDelayed(eq(showLoadingContainerRunnable), anyLong());
}
@Test
public void onRebuildComplete_shouldCancelDelayedRunnable() {
final Context context = RuntimeEnvironment.application;
final ManageApplications fragment = mock(ManageApplications.class);
final View loadingContainer = mock(View.class);
when(loadingContainer.getContext()).thenReturn(context);
final View listContainer = mock(View.class);
when(listContainer.getVisibility()).thenReturn(View.INVISIBLE);
when(listContainer.getContext()).thenReturn(context);
ReflectionHelpers.setField(fragment, "mLoadingContainer", loadingContainer);
ReflectionHelpers.setField(fragment, "mListContainer", listContainer);
when(fragment.getActivity()).thenReturn(mock(Activity.class));
final Runnable showLoadingContainerRunnable = mock(Runnable.class);
final Handler handler = mock(Handler.class);
final ManageApplications.ApplicationsAdapter adapter =
spy(new ManageApplications.ApplicationsAdapter(mState, fragment, 0));
ReflectionHelpers.setField(adapter, "mShowLoadingContainerRunnable",
showLoadingContainerRunnable);
ReflectionHelpers.setField(adapter, "mFgHandler", handler);
ReflectionHelpers.setField(adapter, "mFilterMode", -1);
// app loading not yet completed
ReflectionHelpers.setField(adapter, "mHasReceivedLoadEntries", false);
adapter.updateLoading();
// app loading completed
ReflectionHelpers.setField(adapter, "mHasReceivedLoadEntries", true);
final ArrayList<ApplicationsState.AppEntry> appList = new ArrayList<>();
appList.add(mock(ApplicationsState.AppEntry.class));
when(mSession.getAllApps()).thenReturn(appList);
adapter.onRebuildComplete(null);
verify(handler).removeCallbacks(showLoadingContainerRunnable);
}
private void setUpOptionMenus() { private void setUpOptionMenus() {
when(mMenu.findItem(anyInt())).thenAnswer(invocation -> { when(mMenu.findItem(anyInt())).thenAnswer(invocation -> {
final Object[] args = invocation.getArguments(); final Object[] args = invocation.getArguments();