diff --git a/res/xml/app_data_usage.xml b/res/xml/app_data_usage.xml index e64a1c5cc63..e94c4ff4ca2 100644 --- a/res/xml/app_data_usage.xml +++ b/res/xml/app_data_usage.xml @@ -21,8 +21,7 @@ android:title="@string/data_usage_app_summary_title"> + android:key="cycle" /> @@ -31,19 +30,22 @@ android:key="total_usage" android:title="@string/total_size_label" android:selectable="false" - android:layout="@layout/horizontal_preference" /> + android:layout="@layout/horizontal_preference" + android:summary="@string/summary_placeholder" /> + android:layout="@layout/horizontal_preference" + android:summary="@string/summary_placeholder" /> + android:layout="@layout/horizontal_preference" + android:summary="@string/summary_placeholder" /> diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java index 460f3909f74..531a341c95a 100644 --- a/src/com/android/settings/datausage/AppDataUsage.java +++ b/src/com/android/settings/datausage/AppDataUsage.java @@ -144,8 +144,7 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE); mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE); - mCycle = findPreference(KEY_CYCLE); - mCycleAdapter = new CycleAdapter(mContext, mCycle, mCycleListener); + initCycle(); final UidDetailProvider uidDetailProvider = getUidDetailProvider(); @@ -211,6 +210,8 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC removePreference(KEY_RESTRICT_BACKGROUND); removePreference(KEY_APP_LIST); } + + addEntityHeader(); } @Override @@ -276,6 +277,17 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC return new UidDetailProvider(mContext); } + private void initCycle() { + mCycle = findPreference(KEY_CYCLE); + mCycleAdapter = new CycleAdapter(mContext, mCycle, mCycleListener); + if (mCycles != null) { + // If coming from a page like DataUsageList where already has a selected cycle, display + // that before loading to reduce flicker. + mCycleAdapter.setInitialCycleList(mCycles, mSelectedCycle); + mCycle.setHasCycles(true); + } + } + private void updatePrefs(boolean restrictBackground, boolean unrestrictData) { final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfMeteredDataRestricted( mContext, mPackageName, UserHandle.getUserId(mAppItem.key)); @@ -308,9 +320,9 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC final long backgroundBytes, foregroundBytes; if (mUsageData == null || position >= mUsageData.size()) { backgroundBytes = foregroundBytes = 0; - mCycle.setVisible(false); + mCycle.setHasCycles(false); } else { - mCycle.setVisible(true); + mCycle.setHasCycles(true); final NetworkCycleDataForUid data = mUsageData.get(position); backgroundBytes = data.getBackgroudUsage(); foregroundBytes = data.getForegroudUsage(); @@ -335,10 +347,8 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC return false; } - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - + @VisibleForTesting + void addEntityHeader() { String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null; int uid = 0; if (pkg != null) { diff --git a/src/com/android/settings/datausage/CycleAdapter.java b/src/com/android/settings/datausage/CycleAdapter.java index 2cabd8de452..b41b6aad91b 100644 --- a/src/com/android/settings/datausage/CycleAdapter.java +++ b/src/com/android/settings/datausage/CycleAdapter.java @@ -13,24 +13,13 @@ */ package com.android.settings.datausage; -import android.annotation.NonNull; -import android.app.usage.NetworkStats; import android.content.Context; -import android.net.NetworkPolicy; -import android.net.NetworkPolicyManager; -import android.text.format.DateUtils; -import android.util.Pair; -import android.util.Range; import android.widget.AdapterView; -import com.android.net.module.util.NetworkStatsUtils; import com.android.settings.Utils; -import com.android.settingslib.net.ChartData; import com.android.settingslib.net.NetworkCycleData; import com.android.settingslib.widget.SettingsSpinnerAdapter; -import java.time.ZonedDateTime; -import java.util.Iterator; import java.util.List; import java.util.Objects; @@ -45,7 +34,6 @@ public class CycleAdapter extends SettingsSpinnerAdapter mSpinner = spinner; mListener = listener; mSpinner.setAdapter(this); - mSpinner.setOnItemSelectedListener(mListener); } /** @@ -65,128 +53,14 @@ public class CycleAdapter extends SettingsSpinnerAdapter return 0; } - protected static long getTotalBytesForTimeRange(List stats, - Range range) { - long bytes = 0L; - for (NetworkStats.Bucket bucket : stats) { - final Range bucketSpan = new Range<>( - bucket.getStartTimeStamp(), bucket.getEndTimeStamp()); - // Only record bytes that overlapped with the given time range. For partially - // overlapped bucket, record rational bytes assuming the traffic is uniform - // distributed within the bucket. - try { - final Range overlapped = range.intersect(bucketSpan); - final long totalOfBucket = bucket.getRxBytes() + bucket.getTxBytes(); - bytes += NetworkStatsUtils.multiplySafeByRational(totalOfBucket, - overlapped.getUpper() - overlapped.getLower(), - bucketSpan.getUpper() - bucketSpan.getLower()); - } catch (IllegalArgumentException e) { - // Range disjoint, ignore. - continue; - } - } - return bytes; - } - - @NonNull - private Range getTimeRangeOf(@NonNull List stats) { - long start = Long.MAX_VALUE; - long end = Long.MIN_VALUE; - for (NetworkStats.Bucket bucket : stats) { - start = Math.min(start, bucket.getStartTimeStamp()); - end = Math.max(end, bucket.getEndTimeStamp()); - } - return new Range(start, end); - } - - /** - * Rebuild list based on {@link NetworkPolicy} and available - * {@link List} data. Always selects the newest item, - * updating the inspection range on chartData. - */ - @Deprecated - public boolean updateCycleList(NetworkPolicy policy, ChartData chartData) { - // stash away currently selected cycle to try restoring below - final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem) - mSpinner.getSelectedItem(); + void setInitialCycleList(List cycles, long selectedCycle) { clear(); - - final Context context = getContext(); - - long historyStart; - long historyEnd; - try { - final Range historyTimeRange = getTimeRangeOf(chartData.network); - historyStart = historyTimeRange.getLower(); - historyEnd = historyTimeRange.getUpper(); - } catch (IllegalArgumentException e) { - // Empty history. - final long now = System.currentTimeMillis(); - historyStart = now; - historyEnd = now + 1; - } - - boolean hasCycles = false; - if (policy != null) { - final Iterator> it = NetworkPolicyManager - .cycleIterator(policy); - while (it.hasNext()) { - final Pair cycle = it.next(); - final long cycleStart = cycle.first.toInstant().toEpochMilli(); - final long cycleEnd = cycle.second.toInstant().toEpochMilli(); - - final boolean includeCycle; - if (chartData != null) { - final long bytesInCycle = getTotalBytesForTimeRange(chartData.network, - new Range<>(cycleStart, cycleEnd)); - includeCycle = bytesInCycle > 0; - } else { - includeCycle = true; - } - - if (includeCycle) { - add(new CycleAdapter.CycleItem(context, cycleStart, cycleEnd)); - hasCycles = true; - } + for (int i = 0; i < cycles.size() - 1; i++) { + add(new CycleAdapter.CycleItem(getContext(), cycles.get(i + 1), cycles.get(i))); + if (cycles.get(i) == selectedCycle) { + mSpinner.setSelection(i); } } - - if (!hasCycles) { - // no policy defined cycles; show entry for each four-week period - long cycleEnd = historyEnd; - while (cycleEnd > historyStart) { - final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4); - - final boolean includeCycle; - if (chartData != null) { - final long bytesInCycle = getTotalBytesForTimeRange(chartData.network, - new Range<>(cycleStart, cycleEnd)); - includeCycle = bytesInCycle > 0; - } else { - includeCycle = true; - } - - if (includeCycle) { - add(new CycleAdapter.CycleItem(context, cycleStart, cycleEnd)); - } - cycleEnd = cycleStart; - } - } - - // force pick the current cycle (first item) - 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; } /** @@ -194,6 +68,7 @@ public class CycleAdapter extends SettingsSpinnerAdapter * updating the inspection range on chartData. */ public boolean updateCycleList(List cycleData) { + mSpinner.setOnItemSelectedListener(mListener); // stash away currently selected cycle to try restoring below final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem) mSpinner.getSelectedItem(); @@ -228,10 +103,6 @@ public class CycleAdapter extends SettingsSpinnerAdapter public long start; public long end; - public CycleItem(CharSequence label) { - this.label = label; - } - public CycleItem(Context context, long start, long end) { this.label = Utils.formatDateRange(context, start, end); this.start = start; diff --git a/src/com/android/settings/datausage/SpinnerPreference.java b/src/com/android/settings/datausage/SpinnerPreference.java index 867930baa97..c4b7a4e18da 100644 --- a/src/com/android/settings/datausage/SpinnerPreference.java +++ b/src/com/android/settings/datausage/SpinnerPreference.java @@ -31,6 +31,8 @@ public class SpinnerPreference extends Preference implements CycleAdapter.Spinne private AdapterView.OnItemSelectedListener mListener; private Object mCurrentObject; private int mPosition; + private View mItemView; + private boolean mItemViewVisible = false; public SpinnerPreference(Context context, AttributeSet attrs) { super(context, attrs); @@ -63,12 +65,24 @@ public class SpinnerPreference extends Preference implements CycleAdapter.Spinne @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); + mItemView = holder.itemView; + mItemView.setVisibility(mItemViewVisible ? View.VISIBLE : View.INVISIBLE); Spinner spinner = (Spinner) holder.findViewById(R.id.cycles_spinner); spinner.setAdapter(mAdapter); spinner.setSelection(mPosition); spinner.setOnItemSelectedListener(mOnSelectedListener); } + void setHasCycles(boolean hasData) { + setVisible(hasData); + if (hasData) { + mItemViewVisible = true; + if (mItemView != null) { + mItemView.setVisibility(View.VISIBLE); + } + } + } + @Override protected void performClick(View view) { view.findViewById(R.id.cycles_spinner).performClick(); diff --git a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java index 09c57340d60..f043ec737e3 100644 --- a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java +++ b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java @@ -43,7 +43,6 @@ import android.os.Process; import android.telephony.SubscriptionManager; import android.text.format.DateUtils; import android.util.ArraySet; -import android.view.View; import androidx.fragment.app.FragmentActivity; import androidx.preference.Preference; @@ -96,6 +95,10 @@ public class AppDataUsageTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); + + ShadowEntityHeaderController.setUseMock(mHeaderController); + when(mHeaderController.setRecyclerView(any(), any())).thenReturn(mHeaderController); + when(mHeaderController.setUid(anyInt())).thenReturn(mHeaderController); } @After @@ -163,10 +166,6 @@ public class AppDataUsageTest { @Test public void bindAppHeader_allWorkApps_shouldNotShowAppInfoLink() { - ShadowEntityHeaderController.setUseMock(mHeaderController); - when(mHeaderController.setRecyclerView(any(), any())).thenReturn(mHeaderController); - when(mHeaderController.setUid(anyInt())).thenReturn(mHeaderController); - mFragment = spy(new AppDataUsage()); when(mFragment.getPreferenceManager()) @@ -174,7 +173,7 @@ public class AppDataUsageTest { doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); ReflectionHelpers.setField(mFragment, "mAppItem", mock(AppItem.class)); - mFragment.onViewCreated(new View(RuntimeEnvironment.application), new Bundle()); + mFragment.addEntityHeader(); verify(mHeaderController).setHasAppInfoLink(false); } @@ -196,16 +195,13 @@ public class AppDataUsageTest { when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())) .thenReturn(fakeUserId); - ShadowEntityHeaderController.setUseMock(mHeaderController); - when(mHeaderController.setRecyclerView(any(), any())).thenReturn(mHeaderController); - when(mHeaderController.setUid(fakeUserId)).thenReturn(mHeaderController); when(mHeaderController.setHasAppInfoLink(anyBoolean())).thenReturn(mHeaderController); when(mFragment.getPreferenceManager()) .thenReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS)); doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); - mFragment.onViewCreated(new View(RuntimeEnvironment.application), new Bundle()); + mFragment.addEntityHeader(); verify(mHeaderController).setHasAppInfoLink(true); verify(mHeaderController).setUid(fakeUserId); @@ -268,7 +264,7 @@ public class AppDataUsageTest { mFragment.bindData(0 /* position */); - verify(cycle).setVisible(false); + verify(cycle).setHasCycles(false); } @Test @@ -293,7 +289,7 @@ public class AppDataUsageTest { mFragment.bindData(0 /* position */); - verify(cycle).setVisible(true); + verify(cycle).setHasCycles(true); verify(totalPref).setSummary( DataUsageUtils.formatDataUsage(context, backgroundBytes + foregroundBytes)); verify(backgroundPref).setSummary(DataUsageUtils.formatDataUsage(context, backgroundBytes));