Fix flicker for Data Usage page
Both "Mobile data usage" & "Non-carrier data usage". By, 1. Use summary placeholder for usage amount to avoid shift 2. Before fix CycleListener's onItemSelected() is called multiple times, cause the app list to flash, let DataUsageList to handle the dedup logic to better handling. 3. Before fix if return from App Usage page, no loading view is displayed (only first enter has it), move this to onResume() to fix. 4. Before fix the cycles passed to App Usage page is cached (even when the cycles are changed), clear the cache when onResume() to fix. 5. Listener in SpinnerPreference could be null, add safety guard to it. Cherry-pick from Change-Id: I95e544c46333496f4f30ed77dafa4779b4d66019 Fix: 277162513 Test: manual visual test Test: Unit test Change-Id: I50fb79aa3c888651a79fd1d030da554bebb6a660
This commit is contained in:
@@ -17,7 +17,8 @@
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="usage_amount">
|
||||
android:key="usage_amount"
|
||||
android:title="@string/summary_placeholder">
|
||||
|
||||
<com.android.settings.datausage.ChartDataUsagePreference
|
||||
android:key="chart_data" />
|
||||
|
@@ -21,7 +21,6 @@ import com.android.settingslib.net.NetworkCycleData;
|
||||
import com.android.settingslib.widget.SettingsSpinnerAdapter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
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,
|
||||
* updating the inspection range on chartData.
|
||||
*/
|
||||
public boolean updateCycleList(List<? extends NetworkCycleData> cycleData) {
|
||||
public void updateCycleList(List<? extends NetworkCycleData> cycleData) {
|
||||
mSpinner.setOnItemSelectedListener(mListener);
|
||||
// stash away currently selected cycle to try restoring below
|
||||
final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem)
|
||||
@@ -83,17 +82,8 @@ public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem>
|
||||
if (getCount() > 0) {
|
||||
final int position = findNearestPosition(previousItem);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* List item that reflects a specific data usage cycle.
|
||||
|
@@ -75,6 +75,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -118,7 +119,11 @@ public class DataUsageList extends DataUsageBaseFragment
|
||||
|
||||
private ChartDataUsagePreference mChart;
|
||||
private List<NetworkCycleChartData> mCycleData;
|
||||
// Caches the cycles for startAppDataUsage usage, which need be cleared when resumed.
|
||||
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 CycleAdapter mCycleAdapter;
|
||||
private Preference mUsageAmount;
|
||||
@@ -219,13 +224,15 @@ public class DataUsageList extends DataUsageBaseFragment
|
||||
|
||||
mLoadingViewController = new LoadingViewController(
|
||||
getView().findViewById(R.id.loading_container), getListView());
|
||||
mLoadingViewController.showLoadingViewDelayed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mLoadingViewController.showLoadingViewDelayed();
|
||||
mDataStateListener.start(mSubId);
|
||||
mCycles = null;
|
||||
mLastDisplayedCycle = null;
|
||||
|
||||
// kick off loader for network history
|
||||
// TODO: consider chaining two loaders together instead of reloading
|
||||
@@ -348,9 +355,46 @@ public class DataUsageList extends DataUsageBaseFragment
|
||||
}
|
||||
|
||||
// generate cycle list based on policy and available history
|
||||
if (mCycleAdapter.updateCycleList(mCycleData)) {
|
||||
updateDetailData();
|
||||
mCycleAdapter.updateCycleList(mCycleData);
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -540,33 +584,10 @@ public class DataUsageList extends DataUsageBaseFragment
|
||||
return Math.max(largest, item.total);
|
||||
}
|
||||
|
||||
private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
|
||||
private final OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
final CycleAdapter.CycleItem cycle = (CycleAdapter.CycleItem)
|
||||
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();
|
||||
updateSelectedCycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.android.settings.datausage;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
@@ -28,6 +29,7 @@ import com.android.settings.R;
|
||||
public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface {
|
||||
|
||||
private CycleAdapter mAdapter;
|
||||
@Nullable
|
||||
private AdapterView.OnItemSelectedListener mListener;
|
||||
private Object mCurrentObject;
|
||||
private int mPosition;
|
||||
@@ -88,19 +90,24 @@ public class SpinnerPreference extends Preference implements CycleAdapter.Spinne
|
||||
view.findViewById(R.id.cycles_spinner).performClick();
|
||||
}
|
||||
|
||||
private final AdapterView.OnItemSelectedListener mOnSelectedListener
|
||||
= new AdapterView.OnItemSelectedListener() {
|
||||
private final AdapterView.OnItemSelectedListener mOnSelectedListener =
|
||||
new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
public void onItemSelected(
|
||||
AdapterView<?> parent, View view, int position, long id) {
|
||||
if (mPosition == position) return;
|
||||
mPosition = position;
|
||||
mCurrentObject = mAdapter.getItem(position);
|
||||
if (mListener != null) {
|
||||
mListener.onItemSelected(parent, view, position, id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
if (mListener != null) {
|
||||
mListener.onNothingSelected(parent);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -103,6 +103,7 @@ public class DataUsageListTest {
|
||||
mMobileDataEnabledListener);
|
||||
ReflectionHelpers.setField(mDataUsageList, "services", mNetworkServices);
|
||||
doReturn(mLoaderManager).when(mDataUsageList).getLoaderManager();
|
||||
mDataUsageList.mLoadingViewController = mock(LoadingViewController.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -237,8 +238,6 @@ public class DataUsageListTest {
|
||||
|
||||
@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;
|
||||
|
Reference in New Issue
Block a user