From b8491032d75a6c021b75f769d37d2849981476f8 Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Mon, 14 Mar 2022 09:45:33 +0800 Subject: [PATCH] [DataUsage] Adjusting the control of display sequence Showing a usage graph before end of statistic would lead to incorrect height of usage graph, and another update would lead to layout moved a little bit. This change tries to improve it through: 1. Start the loading animation earlier, and stop animation when statistics loaded. (Only effective when UI re-create.) 2. Update the UI only when statistics are ready. Bug: 187019210 Test: robotest ChartDataUsagePreferenceTest DataUsageListTest Change-Id: Ic83f2422b6c6d55948110d652ee24234f43b6445 --- .../datausage/ChartDataUsagePreference.java | 46 ++++++++++++++----- .../settings/datausage/DataUsageList.java | 29 ++++++++---- .../ChartDataUsagePreferenceTest.java | 38 +++++++++------ .../settings/datausage/DataUsageListTest.java | 45 +++++++++--------- 4 files changed, 102 insertions(+), 56 deletions(-) diff --git a/src/com/android/settings/datausage/ChartDataUsagePreference.java b/src/com/android/settings/datausage/ChartDataUsagePreference.java index 12fb03b6258..286d9e5195d 100644 --- a/src/com/android/settings/datausage/ChartDataUsagePreference.java +++ b/src/com/android/settings/datausage/ChartDataUsagePreference.java @@ -48,8 +48,8 @@ public class ChartDataUsagePreference extends Preference { // Set to half a meg for now. private static final long RESOLUTION = DataUnit.MEBIBYTES.toBytes(1) / 2; - private final int mWarningColor; - private final int mLimitColor; + private int mWarningColor; + private int mLimitColor; private Resources mResources; private NetworkPolicy mPolicy; @@ -58,24 +58,25 @@ public class ChartDataUsagePreference extends Preference { private NetworkCycleChartData mNetworkCycleChartData; private int mSecondaryColor; private int mSeriesColor; + private UsageView mUsageView; + private boolean mSuspendUiUpdate; // Suppress UI updates to save some CPU time. public ChartDataUsagePreference(Context context, AttributeSet attrs) { super(context, attrs); - mResources = context.getResources(); setSelectable(false); - mLimitColor = Utils.getColorAttrDefaultColor(context, android.R.attr.colorError); - mWarningColor = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary); - setLayoutResource(R.layout.data_usage_graph); } @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); - final UsageView chart = (UsageView) holder.findViewById(R.id.data_usage); - if (mNetworkCycleChartData == null) { + mUsageView = (UsageView) holder.findViewById(R.id.data_usage); + } + + private void onUpdateView() { + UsageView chart = mUsageView; + if ((chart == null) || (mNetworkCycleChartData == null)) { return; } - final int top = getTop(); chart.clearPaths(); chart.configureGraph(toInt(mEnd - mStart), top); @@ -291,9 +292,16 @@ public class ChartDataUsagePreference extends Preference { return new SpannableStringBuilder().append(label, new ForegroundColorSpan(mLimitColor), 0); } + public void onPreparingChartData() { + mSuspendUiUpdate = true; + } + public void setNetworkPolicy(NetworkPolicy policy) { mPolicy = policy; - notifyChanged(); + if ((!mSuspendUiUpdate) && (mResources != null)) { + onUpdateView(); + notifyChanged(); + } } public long getInspectStart() { @@ -305,15 +313,31 @@ public class ChartDataUsagePreference extends Preference { } public void setNetworkCycleData(NetworkCycleChartData data) { + if (data == null) { + return; + } mNetworkCycleChartData = data; mStart = data.getStartTime(); mEnd = data.getEndTime(); + if (mResources == null) { + Context context = getContext(); + mResources = context.getResources(); + mLimitColor = Utils.getColorAttrDefaultColor(context, android.R.attr.colorError); + mWarningColor = Utils.getColorAttrDefaultColor(context, + android.R.attr.textColorSecondary); + setLayoutResource(R.layout.data_usage_graph); + } + onUpdateView(); notifyChanged(); + mSuspendUiUpdate = false; } public void setColors(int seriesColor, int secondaryColor) { mSeriesColor = seriesColor; mSecondaryColor = secondaryColor; - notifyChanged(); + if ((!mSuspendUiUpdate) && (mResources != null)) { + onUpdateView(); + notifyChanged(); + } } } diff --git a/src/com/android/settings/datausage/DataUsageList.java b/src/com/android/settings/datausage/DataUsageList.java index dc945f1fc42..2f4054d1def 100644 --- a/src/com/android/settings/datausage/DataUsageList.java +++ b/src/com/android/settings/datausage/DataUsageList.java @@ -151,7 +151,20 @@ public class DataUsageList extends DataUsageBaseFragment public void onViewCreated(View v, Bundle savedInstanceState) { super.onViewCreated(v, savedInstanceState); + // Show loading + mLoadingViewController = new LoadingViewController( + v.findViewById(R.id.loading_container), getListView()); + mLoadingViewController.showLoadingViewDelayed(); + } + + private void onEndOfLoading() { + if (mHeader != null) { + return; + } mHeader = setPinnedHeaderView(R.layout.apps_filter_spinner); + + mCycleSpinner = mHeader.findViewById(R.id.filter_spinner); + mHeader.findViewById(R.id.filter_settings).setOnClickListener(btn -> { final Bundle args = new Bundle(); args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate); @@ -162,8 +175,6 @@ public class DataUsageList extends DataUsageBaseFragment .setArguments(args) .launch(); }); - mCycleSpinner = mHeader.findViewById(R.id.filter_spinner); - mCycleSpinner.setVisibility(View.GONE); mCycleAdapter = new CycleAdapter(mCycleSpinner.getContext(), new SpinnerInterface() { @Override public void setAdapter(CycleAdapter cycleAdapter) { @@ -195,10 +206,6 @@ public class DataUsageList extends DataUsageBaseFragment super.sendAccessibilityEvent(host, eventType); } }); - - mLoadingViewController = new LoadingViewController( - getView().findViewById(R.id.loading_container), getListView()); - mLoadingViewController.showLoadingViewDelayed(); } @Override @@ -206,6 +213,10 @@ public class DataUsageList extends DataUsageBaseFragment super.onResume(); mDataStateListener.start(mSubId); + if (mChart != null) { + mChart.onPreparingChartData(); + } + // kick off loader for network history // TODO: consider chaining two loaders together instead of reloading // network history when showing app detail. @@ -526,11 +537,13 @@ public class DataUsageList extends DataUsageBaseFragment @Override public void onLoadFinished(Loader> loader, List data) { - mLoadingViewController.showContent(false /* animate */); + onEndOfLoading(); + if (mLoadingViewController != null) { + mLoadingViewController.showContent(false /* animate */); + } mCycleData = data; // calculate policy cycles based on available data updatePolicy(); - mCycleSpinner.setVisibility(View.VISIBLE); } @Override diff --git a/tests/robotests/src/com/android/settings/datausage/ChartDataUsagePreferenceTest.java b/tests/robotests/src/com/android/settings/datausage/ChartDataUsagePreferenceTest.java index aab67be1de9..3be652dbdde 100644 --- a/tests/robotests/src/com/android/settings/datausage/ChartDataUsagePreferenceTest.java +++ b/tests/robotests/src/com/android/settings/datausage/ChartDataUsagePreferenceTest.java @@ -17,7 +17,10 @@ package com.android.settings.datausage; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -163,23 +166,32 @@ public class ChartDataUsagePreferenceTest { @Test public void notifyChange_nonEmptyDataUsage_shouldHaveSingleContentDescription() { - final UsageView chart = (UsageView) mHolder.findViewById(R.id.data_usage); - final TextView labelTop = (TextView) mHolder.findViewById(R.id.label_top); - final TextView labelMiddle = (TextView) mHolder.findViewById(R.id.label_middle); - final TextView labelBottom = (TextView) mHolder.findViewById(R.id.label_bottom); - final TextView labelStart = (TextView) mHolder.findViewById(R.id.label_start); - final TextView labelEnd = (TextView) mHolder.findViewById(R.id.label_end); + final ArgumentCaptor contentCaptor = + ArgumentCaptor.forClass(CharSequence.class); + final UsageView chart = mock(UsageView.class); + doReturn(chart).when(mHolder).findViewById(R.id.data_usage); + final TextView labelTop = mock(TextView.class); + doReturn(labelTop).when(mHolder).findViewById(R.id.label_top); + final TextView labelMiddle = mock(TextView.class); + doReturn(labelMiddle).when(mHolder).findViewById(R.id.label_middle); + final TextView labelBottom = mock(TextView.class); + doReturn(labelBottom).when(mHolder).findViewById(R.id.label_bottom); + final TextView labelStart = mock(TextView.class); + doReturn(labelStart).when(mHolder).findViewById(R.id.label_start); + final TextView labelEnd = mock(TextView.class); + doReturn(labelEnd).when(mHolder).findViewById(R.id.label_end); createTestNetworkData(); - mPreference.setNetworkCycleData(mNetworkCycleChartData); mPreference.onBindViewHolder(mHolder); + mPreference.setNetworkCycleData(mNetworkCycleChartData); - assertThat(chart.getContentDescription()).isNotNull(); - assertThat(labelTop.getContentDescription()).isNull(); - assertThat(labelMiddle.getContentDescription()).isNull(); - assertThat(labelBottom.getContentDescription()).isNull(); - assertThat(labelStart.getContentDescription()).isNull(); - assertThat(labelEnd.getContentDescription()).isNull(); + verify(chart).setContentDescription(contentCaptor.capture()); + assertThat(contentCaptor.getValue()).isNotNull(); + verify(labelTop, never()).setContentDescription(any()); + verify(labelMiddle, never()).setContentDescription(any()); + verify(labelBottom, never()).setContentDescription(any()); + verify(labelStart, never()).setContentDescription(any()); + verify(labelEnd, never()).setContentDescription(any()); } @Test diff --git a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java index e4f5b1fc9e9..3a10132064a 100644 --- a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java +++ b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java @@ -192,32 +192,23 @@ public class DataUsageListTest { } @Test - public void onViewCreated_shouldHideCycleSpinner() { - final View view = new View(mActivity); - final View header = getHeader(); - final Spinner spinner = getSpinner(header); - spinner.setVisibility(View.VISIBLE); - doReturn(header).when(mDataUsageList).setPinnedHeaderView(anyInt()); - doReturn(view).when(mDataUsageList).getView(); + public void onViewCreated_shouldNotSetCycleSpinner() { + View view = constructRootView(); mDataUsageList.onViewCreated(view, null); - assertThat(spinner.getVisibility()).isEqualTo(View.GONE); + assertThat(getSpinner(view)).isNull(); } @Test - public void onLoadFinished_networkCycleDataCallback_shouldShowCycleSpinner() { - final LoadingViewController loadingViewController = mock(LoadingViewController.class); - mDataUsageList.mLoadingViewController = loadingViewController; - final Spinner spinner = getSpinner(getHeader()); - spinner.setVisibility(View.INVISIBLE); - mDataUsageList.mCycleSpinner = spinner; - assertThat(spinner.getVisibility()).isEqualTo(View.INVISIBLE); + public void onLoadFinished_networkCycleDataCallback_shouldSetCycleSpinner() { + final View view = constructRootView(); doNothing().when(mDataUsageList).updatePolicy(); + doReturn(setupHeaderView(view)).when(mDataUsageList).setPinnedHeaderView(anyInt()); mDataUsageList.mNetworkCycleDataCallbacks.onLoadFinished(null, null); - assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(getSpinner(view)).isNotNull(); } @Test @@ -228,18 +219,24 @@ public class DataUsageListTest { verify(mLoaderManager).destroyLoader(DataUsageList.LOADER_SUMMARY); } - private View getHeader() { - final View rootView = LayoutInflater.from(mActivity) + private View constructRootView() { + View rootView = LayoutInflater.from(mActivity) .inflate(R.layout.preference_list_fragment, null, false); - final FrameLayout pinnedHeader = rootView.findViewById(R.id.pinned_header); - final View header = mActivity.getLayoutInflater() - .inflate(R.layout.apps_filter_spinner, pinnedHeader, false); + return rootView; + } + private View setupHeaderView(View rootView) { + final FrameLayout pinnedHeader = (FrameLayout) rootView.findViewById(R.id.pinned_header); + final View header = mActivity.getLayoutInflater() + .inflate(R.layout.apps_filter_spinner, pinnedHeader, true); return header; } - private Spinner getSpinner(View header) { - final Spinner spinner = header.findViewById(R.id.filter_spinner); - return spinner; + private Spinner getSpinner(View rootView) { + final FrameLayout pinnedHeader = (FrameLayout) rootView.findViewById(R.id.pinned_header); + if (pinnedHeader == null) { + return null; + } + return pinnedHeader.findViewById(R.id.filter_spinner); } }