Merge changes I50fb79aa,I5a84a9b1,Iea61c06d into udc-dev

* changes:
  Fix flicker for Data Usage page
  Revert "[DataUsage] Adjusting the control of display sequence"
  Revert "[Settings] Adjusting the control of display sequence (part 2)"
This commit is contained in:
Chaohui Wang
2023-04-23 05:14:01 +00:00
committed by Android (Google) Code Review
7 changed files with 134 additions and 164 deletions

View File

@@ -17,7 +17,8 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory <PreferenceCategory
android:key="usage_amount"> android:key="usage_amount"
android:title="@string/summary_placeholder">
<com.android.settings.datausage.ChartDataUsagePreference <com.android.settings.datausage.ChartDataUsagePreference
android:key="chart_data" /> android:key="chart_data" />

View File

@@ -48,8 +48,8 @@ public class ChartDataUsagePreference extends Preference {
// Set to half a meg for now. // Set to half a meg for now.
private static final long RESOLUTION = DataUnit.MEBIBYTES.toBytes(1) / 2; private static final long RESOLUTION = DataUnit.MEBIBYTES.toBytes(1) / 2;
private int mWarningColor; private final int mWarningColor;
private int mLimitColor; private final int mLimitColor;
private Resources mResources; private Resources mResources;
private NetworkPolicy mPolicy; private NetworkPolicy mPolicy;
@@ -58,35 +58,35 @@ public class ChartDataUsagePreference extends Preference {
private NetworkCycleChartData mNetworkCycleChartData; private NetworkCycleChartData mNetworkCycleChartData;
private int mSecondaryColor; private int mSecondaryColor;
private int mSeriesColor; private int mSeriesColor;
private UsageView mUsageView;
private boolean mSuspendUiUpdate; // Suppress UI updates to save some CPU time.
public ChartDataUsagePreference(Context context, AttributeSet attrs) { public ChartDataUsagePreference(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
mResources = context.getResources();
setSelectable(false); 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 @Override
public void onBindViewHolder(PreferenceViewHolder holder) { public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder); super.onBindViewHolder(holder);
mUsageView = (UsageView) holder.findViewById(R.id.data_usage); final UsageView chart = (UsageView) holder.findViewById(R.id.data_usage);
} if (mNetworkCycleChartData == null) {
private void onUpdateView() {
if ((mUsageView == null) || (mNetworkCycleChartData == null)) {
return; return;
} }
final int top = getTop(); final int top = getTop();
mUsageView.clearPaths(); chart.clearPaths();
mUsageView.configureGraph(toInt(mEnd - mStart), top); chart.configureGraph(toInt(mEnd - mStart), top);
calcPoints(mUsageView, mNetworkCycleChartData.getUsageBuckets()); calcPoints(chart, mNetworkCycleChartData.getUsageBuckets());
setupContentDescription(mUsageView, mNetworkCycleChartData.getUsageBuckets()); setupContentDescription(chart, mNetworkCycleChartData.getUsageBuckets());
mUsageView.setBottomLabels(new CharSequence[] { chart.setBottomLabels(new CharSequence[] {
Utils.formatDateRange(getContext(), mStart, mStart), Utils.formatDateRange(getContext(), mStart, mStart),
Utils.formatDateRange(getContext(), mEnd, mEnd), Utils.formatDateRange(getContext(), mEnd, mEnd),
}); });
bindNetworkPolicy(mUsageView, mPolicy, top); bindNetworkPolicy(chart, mPolicy, top);
} }
public int getTop() { public int getTop() {
@@ -291,16 +291,9 @@ public class ChartDataUsagePreference extends Preference {
return new SpannableStringBuilder().append(label, new ForegroundColorSpan(mLimitColor), 0); return new SpannableStringBuilder().append(label, new ForegroundColorSpan(mLimitColor), 0);
} }
public void onPreparingChartData() {
mSuspendUiUpdate = true;
}
public void setNetworkPolicy(NetworkPolicy policy) { public void setNetworkPolicy(NetworkPolicy policy) {
mPolicy = policy; mPolicy = policy;
if ((!mSuspendUiUpdate) && (mResources != null)) { notifyChanged();
onUpdateView();
notifyChanged();
}
} }
public long getInspectStart() { public long getInspectStart() {
@@ -312,31 +305,15 @@ public class ChartDataUsagePreference extends Preference {
} }
public void setNetworkCycleData(NetworkCycleChartData data) { public void setNetworkCycleData(NetworkCycleChartData data) {
if (data == null) {
return;
}
mNetworkCycleChartData = data; mNetworkCycleChartData = data;
mStart = data.getStartTime(); mStart = data.getStartTime();
mEnd = data.getEndTime(); 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(); notifyChanged();
mSuspendUiUpdate = false;
} }
public void setColors(int seriesColor, int secondaryColor) { public void setColors(int seriesColor, int secondaryColor) {
mSeriesColor = seriesColor; mSeriesColor = seriesColor;
mSecondaryColor = secondaryColor; mSecondaryColor = secondaryColor;
if ((!mSuspendUiUpdate) && (mResources != null)) { notifyChanged();
onUpdateView();
notifyChanged();
}
} }
} }

View File

@@ -21,7 +21,6 @@ import com.android.settingslib.net.NetworkCycleData;
import com.android.settingslib.widget.SettingsSpinnerAdapter; import com.android.settingslib.widget.SettingsSpinnerAdapter;
import java.util.List; import java.util.List;
import java.util.Objects;
public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem> { public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem> {
@@ -67,7 +66,7 @@ public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem>
* Rebuild list based on network data. Always selects the newest item, * Rebuild list based on network data. Always selects the newest item,
* updating the inspection range on chartData. * updating the inspection range on chartData.
*/ */
public boolean updateCycleList(List<? extends NetworkCycleData> cycleData) { public void updateCycleList(List<? extends NetworkCycleData> cycleData) {
mSpinner.setOnItemSelectedListener(mListener); mSpinner.setOnItemSelectedListener(mListener);
// stash away currently selected cycle to try restoring below // stash away currently selected cycle to try restoring below
final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem) final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem)
@@ -83,16 +82,7 @@ public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem>
if (getCount() > 0) { if (getCount() > 0) {
final int position = findNearestPosition(previousItem); final int position = findNearestPosition(previousItem);
mSpinner.setSelection(position); mSpinner.setSelection(position);
// only force-update cycle when changed; skipping preserves any
// user-defined inspection region.
final CycleAdapter.CycleItem selectedItem = getItem(position);
if (!Objects.equals(selectedItem, previousItem)) {
mListener.onItemSelected(null, null, position, 0);
return false;
}
} }
return true;
} }
/** /**

View File

@@ -75,6 +75,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
/** /**
@@ -118,7 +119,11 @@ public class DataUsageList extends DataUsageBaseFragment
private ChartDataUsagePreference mChart; private ChartDataUsagePreference mChart;
private List<NetworkCycleChartData> mCycleData; private List<NetworkCycleChartData> mCycleData;
// Caches the cycles for startAppDataUsage usage, which need be cleared when resumed.
private ArrayList<Long> mCycles; private ArrayList<Long> mCycles;
// Spinner will keep the selected cycle even after paused, this only keeps the displayed cycle,
// which need be cleared when resumed.
private CycleAdapter.CycleItem mLastDisplayedCycle;
private UidDetailProvider mUidDetailProvider; private UidDetailProvider mUidDetailProvider;
private CycleAdapter mCycleAdapter; private CycleAdapter mCycleAdapter;
private Preference mUsageAmount; private Preference mUsageAmount;
@@ -172,20 +177,7 @@ public class DataUsageList extends DataUsageBaseFragment
public void onViewCreated(View v, Bundle savedInstanceState) { public void onViewCreated(View v, Bundle savedInstanceState) {
super.onViewCreated(v, 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); mHeader = setPinnedHeaderView(R.layout.apps_filter_spinner);
mCycleSpinner = mHeader.findViewById(R.id.filter_spinner);
mHeader.findViewById(R.id.filter_settings).setOnClickListener(btn -> { mHeader.findViewById(R.id.filter_settings).setOnClickListener(btn -> {
final Bundle args = new Bundle(); final Bundle args = new Bundle();
args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate); args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate);
@@ -196,6 +188,8 @@ public class DataUsageList extends DataUsageBaseFragment
.setArguments(args) .setArguments(args)
.launch(); .launch();
}); });
mCycleSpinner = mHeader.findViewById(R.id.filter_spinner);
mCycleSpinner.setVisibility(View.GONE);
mCycleAdapter = new CycleAdapter(mCycleSpinner.getContext(), new SpinnerInterface() { mCycleAdapter = new CycleAdapter(mCycleSpinner.getContext(), new SpinnerInterface() {
@Override @Override
public void setAdapter(CycleAdapter cycleAdapter) { public void setAdapter(CycleAdapter cycleAdapter) {
@@ -227,16 +221,18 @@ public class DataUsageList extends DataUsageBaseFragment
super.sendAccessibilityEvent(host, eventType); super.sendAccessibilityEvent(host, eventType);
} }
}); });
mLoadingViewController = new LoadingViewController(
getView().findViewById(R.id.loading_container), getListView());
} }
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
mLoadingViewController.showLoadingViewDelayed();
mDataStateListener.start(mSubId); mDataStateListener.start(mSubId);
mCycles = null;
if (mChart != null) { mLastDisplayedCycle = null;
mChart.onPreparingChartData();
}
// kick off loader for network history // kick off loader for network history
// TODO: consider chaining two loaders together instead of reloading // TODO: consider chaining two loaders together instead of reloading
@@ -345,9 +341,6 @@ public class DataUsageList extends DataUsageBaseFragment
*/ */
@VisibleForTesting @VisibleForTesting
void updatePolicy() { void updatePolicy() {
if (mHeader == null) {
return;
}
final NetworkPolicy policy = services.mPolicyEditor.getPolicy(mTemplate); final NetworkPolicy policy = services.mPolicyEditor.getPolicy(mTemplate);
final View configureButton = mHeader.findViewById(R.id.filter_settings); final View configureButton = mHeader.findViewById(R.id.filter_settings);
//SUB SELECT //SUB SELECT
@@ -362,9 +355,46 @@ public class DataUsageList extends DataUsageBaseFragment
} }
// generate cycle list based on policy and available history // generate cycle list based on policy and available history
if (mCycleAdapter.updateCycleList(mCycleData)) { mCycleAdapter.updateCycleList(mCycleData);
updateDetailData(); updateSelectedCycle();
}
/**
* Updates the chart and detail data when initial loaded or selected cycle changed.
*/
private void updateSelectedCycle() {
// Avoid from updating UI after #onStop.
if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
return;
} }
// Avoid from updating UI when async query still on-going.
// This could happen when a request from #onMobileDataEnabledChange.
if (mCycleData == null) {
return;
}
final int position = mCycleSpinner.getSelectedItemPosition();
if (mCycleAdapter.getCount() == 0 || position < 0) {
return;
}
final CycleAdapter.CycleItem cycle = mCycleAdapter.getItem(position);
if (Objects.equals(cycle, mLastDisplayedCycle)) {
// Avoid duplicate update to avoid page flash.
return;
}
mLastDisplayedCycle = cycle;
if (LOGD) {
Log.d(TAG, "showing cycle " + cycle + ", [start=" + cycle.start + ", end="
+ cycle.end + "]");
}
// update chart to show selected cycle, and update detail data
// to match updated sweep bounds.
mChart.setNetworkCycleData(mCycleData.get(position));
updateDetailData();
} }
/** /**
@@ -554,33 +584,10 @@ public class DataUsageList extends DataUsageBaseFragment
return Math.max(largest, item.total); return Math.max(largest, item.total);
} }
private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { private final OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
final CycleAdapter.CycleItem cycle = (CycleAdapter.CycleItem) updateSelectedCycle();
mCycleSpinner.getSelectedItem();
if (LOGD) {
Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end="
+ cycle.end + "]");
}
// Avoid from updating UI after #onStop.
if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
return;
}
// Avoid from updating UI when async query still on-going.
// This could happen when a request from #onMobileDataEnabledChange.
if (mCycleData == null) {
return;
}
// update chart to show selected cycle, and update detail data
// to match updated sweep bounds.
mChart.setNetworkCycleData(mCycleData.get(position));
updateDetailData();
} }
@Override @Override
@@ -602,13 +609,11 @@ public class DataUsageList extends DataUsageBaseFragment
@Override @Override
public void onLoadFinished(Loader<List<NetworkCycleChartData>> loader, public void onLoadFinished(Loader<List<NetworkCycleChartData>> loader,
List<NetworkCycleChartData> data) { List<NetworkCycleChartData> data) {
onEndOfLoading(); mLoadingViewController.showContent(false /* animate */);
if (mLoadingViewController != null) {
mLoadingViewController.showContent(false /* animate */);
}
mCycleData = data; mCycleData = data;
// calculate policy cycles based on available data // calculate policy cycles based on available data
updatePolicy(); updatePolicy();
mCycleSpinner.setVisibility(View.VISIBLE);
} }
@Override @Override

View File

@@ -14,6 +14,7 @@
package com.android.settings.datausage; package com.android.settings.datausage;
import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
@@ -28,6 +29,7 @@ import com.android.settings.R;
public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface { public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface {
private CycleAdapter mAdapter; private CycleAdapter mAdapter;
@Nullable
private AdapterView.OnItemSelectedListener mListener; private AdapterView.OnItemSelectedListener mListener;
private Object mCurrentObject; private Object mCurrentObject;
private int mPosition; private int mPosition;
@@ -88,19 +90,24 @@ public class SpinnerPreference extends Preference implements CycleAdapter.Spinne
view.findViewById(R.id.cycles_spinner).performClick(); view.findViewById(R.id.cycles_spinner).performClick();
} }
private final AdapterView.OnItemSelectedListener mOnSelectedListener private final AdapterView.OnItemSelectedListener mOnSelectedListener =
= new AdapterView.OnItemSelectedListener() { new AdapterView.OnItemSelectedListener() {
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(
if (mPosition == position) return; AdapterView<?> parent, View view, int position, long id) {
mPosition = position; if (mPosition == position) return;
mCurrentObject = mAdapter.getItem(position); mPosition = position;
mListener.onItemSelected(parent, view, position, id); mCurrentObject = mAdapter.getItem(position);
} if (mListener != null) {
mListener.onItemSelected(parent, view, position, id);
}
}
@Override @Override
public void onNothingSelected(AdapterView<?> parent) { public void onNothingSelected(AdapterView<?> parent) {
mListener.onNothingSelected(parent); if (mListener != null) {
} mListener.onNothingSelected(parent);
}; }
}
};
} }

View File

@@ -17,10 +17,7 @@ package com.android.settings.datausage;
import static com.google.common.truth.Truth.assertThat; 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.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -166,32 +163,23 @@ public class ChartDataUsagePreferenceTest {
@Test @Test
public void notifyChange_nonEmptyDataUsage_shouldHaveSingleContentDescription() { public void notifyChange_nonEmptyDataUsage_shouldHaveSingleContentDescription() {
final ArgumentCaptor<CharSequence> contentCaptor = final UsageView chart = (UsageView) mHolder.findViewById(R.id.data_usage);
ArgumentCaptor.forClass(CharSequence.class); final TextView labelTop = (TextView) mHolder.findViewById(R.id.label_top);
final UsageView chart = mock(UsageView.class); final TextView labelMiddle = (TextView) mHolder.findViewById(R.id.label_middle);
doReturn(chart).when(mHolder).findViewById(R.id.data_usage); final TextView labelBottom = (TextView) mHolder.findViewById(R.id.label_bottom);
final TextView labelTop = mock(TextView.class); final TextView labelStart = (TextView) mHolder.findViewById(R.id.label_start);
doReturn(labelTop).when(mHolder).findViewById(R.id.label_top); final TextView labelEnd = (TextView) mHolder.findViewById(R.id.label_end);
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(); createTestNetworkData();
mPreference.onBindViewHolder(mHolder);
mPreference.setNetworkCycleData(mNetworkCycleChartData); mPreference.setNetworkCycleData(mNetworkCycleChartData);
verify(chart).setContentDescription(contentCaptor.capture()); mPreference.onBindViewHolder(mHolder);
assertThat(contentCaptor.getValue()).isNotNull();
verify(labelTop, never()).setContentDescription(any()); assertThat(chart.getContentDescription()).isNotNull();
verify(labelMiddle, never()).setContentDescription(any()); assertThat(labelTop.getContentDescription()).isNull();
verify(labelBottom, never()).setContentDescription(any()); assertThat(labelMiddle.getContentDescription()).isNull();
verify(labelStart, never()).setContentDescription(any()); assertThat(labelBottom.getContentDescription()).isNull();
verify(labelEnd, never()).setContentDescription(any()); assertThat(labelStart.getContentDescription()).isNull();
assertThat(labelEnd.getContentDescription()).isNull();
} }
@Test @Test

View File

@@ -103,6 +103,7 @@ public class DataUsageListTest {
mMobileDataEnabledListener); mMobileDataEnabledListener);
ReflectionHelpers.setField(mDataUsageList, "services", mNetworkServices); ReflectionHelpers.setField(mDataUsageList, "services", mNetworkServices);
doReturn(mLoaderManager).when(mDataUsageList).getLoaderManager(); doReturn(mLoaderManager).when(mDataUsageList).getLoaderManager();
mDataUsageList.mLoadingViewController = mock(LoadingViewController.class);
} }
@Test @Test
@@ -222,23 +223,30 @@ public class DataUsageListTest {
} }
@Test @Test
public void onViewCreated_shouldNotSetCycleSpinner() { public void onViewCreated_shouldHideCycleSpinner() {
View view = constructRootView(); 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();
mDataUsageList.onViewCreated(view, null); mDataUsageList.onViewCreated(view, null);
assertThat(getSpinner(view)).isNull(); assertThat(spinner.getVisibility()).isEqualTo(View.GONE);
} }
@Test @Test
public void onLoadFinished_networkCycleDataCallback_shouldSetCycleSpinner() { public void onLoadFinished_networkCycleDataCallback_shouldShowCycleSpinner() {
final View view = constructRootView(); final Spinner spinner = getSpinner(getHeader());
spinner.setVisibility(View.INVISIBLE);
mDataUsageList.mCycleSpinner = spinner;
assertThat(spinner.getVisibility()).isEqualTo(View.INVISIBLE);
doNothing().when(mDataUsageList).updatePolicy(); doNothing().when(mDataUsageList).updatePolicy();
doReturn(setupHeaderView(view)).when(mDataUsageList).setPinnedHeaderView(anyInt());
mDataUsageList.mNetworkCycleDataCallbacks.onLoadFinished(null, null); mDataUsageList.mNetworkCycleDataCallbacks.onLoadFinished(null, null);
assertThat(getSpinner(view)).isNotNull(); assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE);
} }
@Test @Test
@@ -249,25 +257,19 @@ public class DataUsageListTest {
verify(mLoaderManager).destroyLoader(DataUsageList.LOADER_SUMMARY); verify(mLoaderManager).destroyLoader(DataUsageList.LOADER_SUMMARY);
} }
private View constructRootView() { private View getHeader() {
View rootView = LayoutInflater.from(mActivity) final View rootView = LayoutInflater.from(mActivity)
.inflate(R.layout.preference_list_fragment, null, false); .inflate(R.layout.preference_list_fragment, null, false);
return rootView; final FrameLayout pinnedHeader = rootView.findViewById(R.id.pinned_header);
}
private View setupHeaderView(View rootView) {
final FrameLayout pinnedHeader = (FrameLayout) rootView.findViewById(R.id.pinned_header);
final View header = mActivity.getLayoutInflater() final View header = mActivity.getLayoutInflater()
.inflate(R.layout.apps_filter_spinner, pinnedHeader, true); .inflate(R.layout.apps_filter_spinner, pinnedHeader, false);
return header; return header;
} }
private Spinner getSpinner(View rootView) { private Spinner getSpinner(View header) {
final FrameLayout pinnedHeader = (FrameLayout) rootView.findViewById(R.id.pinned_header); final Spinner spinner = header.findViewById(R.id.filter_spinner);
if (pinnedHeader == null) { return spinner;
return null;
}
return pinnedHeader.findViewById(R.id.filter_spinner);
} }
@Implements(DataUsageBaseFragment.class) @Implements(DataUsageBaseFragment.class)