Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b8491032 authored by Bonian Chen's avatar Bonian Chen
Browse files

[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
parent ec9cee3c
Loading
Loading
Loading
Loading
+35 −11
Original line number Diff line number Diff line
@@ -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) {
            return;
        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,10 +292,17 @@ 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;
        if ((!mSuspendUiUpdate) && (mResources != null)) {
            onUpdateView();
            notifyChanged();
        }
    }

    public long getInspectStart() {
        return mStart;
@@ -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;
        if ((!mSuspendUiUpdate) && (mResources != null)) {
            onUpdateView();
            notifyChanged();
        }
    }
}
+21 −8
Original line number Diff line number Diff line
@@ -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<List<NetworkCycleChartData>> loader,
                List<NetworkCycleChartData> data) {
            onEndOfLoading();
            if (mLoadingViewController != null) {
                mLoadingViewController.showContent(false /* animate */);
            }
            mCycleData = data;
            // calculate policy cycles based on available data
            updatePolicy();
            mCycleSpinner.setVisibility(View.VISIBLE);
        }

        @Override
+25 −13
Original line number Diff line number Diff line
@@ -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<CharSequence> 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
+21 −24
Original line number Diff line number Diff line
@@ -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);
    }
}