Loading res/xml/app_data_usage.xml +7 −5 Original line number Original line Diff line number Diff line Loading @@ -21,8 +21,7 @@ android:title="@string/data_usage_app_summary_title"> android:title="@string/data_usage_app_summary_title"> <com.android.settings.datausage.SpinnerPreference <com.android.settings.datausage.SpinnerPreference android:key="cycle" android:key="cycle" /> settings:isPreferenceVisible="false" /> <PreferenceCategory <PreferenceCategory android:key="app_data_usage_summary_category"> android:key="app_data_usage_summary_category"> Loading @@ -31,19 +30,22 @@ android:key="total_usage" android:key="total_usage" android:title="@string/total_size_label" android:title="@string/total_size_label" android:selectable="false" android:selectable="false" android:layout="@layout/horizontal_preference" /> android:layout="@layout/horizontal_preference" android:summary="@string/summary_placeholder" /> <Preference <Preference android:key="foreground_usage" android:key="foreground_usage" android:title="@string/data_usage_label_foreground" android:title="@string/data_usage_label_foreground" android:selectable="false" android:selectable="false" android:layout="@layout/horizontal_preference" /> android:layout="@layout/horizontal_preference" android:summary="@string/summary_placeholder" /> <Preference <Preference android:key="background_usage" android:key="background_usage" android:title="@string/data_usage_label_background" android:title="@string/data_usage_label_background" android:selectable="false" android:selectable="false" android:layout="@layout/horizontal_preference" /> android:layout="@layout/horizontal_preference" android:summary="@string/summary_placeholder" /> </PreferenceCategory> </PreferenceCategory> Loading src/com/android/settings/datausage/AppDataUsage.java +18 −8 Original line number Original line Diff line number Diff line Loading @@ -144,8 +144,7 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE); mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE); mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE); mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE); mCycle = findPreference(KEY_CYCLE); initCycle(); mCycleAdapter = new CycleAdapter(mContext, mCycle, mCycleListener); final UidDetailProvider uidDetailProvider = getUidDetailProvider(); final UidDetailProvider uidDetailProvider = getUidDetailProvider(); Loading Loading @@ -211,6 +210,8 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC removePreference(KEY_RESTRICT_BACKGROUND); removePreference(KEY_RESTRICT_BACKGROUND); removePreference(KEY_APP_LIST); removePreference(KEY_APP_LIST); } } addEntityHeader(); } } @Override @Override Loading Loading @@ -276,6 +277,17 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC return new UidDetailProvider(mContext); 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) { private void updatePrefs(boolean restrictBackground, boolean unrestrictData) { final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfMeteredDataRestricted( final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfMeteredDataRestricted( mContext, mPackageName, UserHandle.getUserId(mAppItem.key)); mContext, mPackageName, UserHandle.getUserId(mAppItem.key)); Loading Loading @@ -308,9 +320,9 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC final long backgroundBytes, foregroundBytes; final long backgroundBytes, foregroundBytes; if (mUsageData == null || position >= mUsageData.size()) { if (mUsageData == null || position >= mUsageData.size()) { backgroundBytes = foregroundBytes = 0; backgroundBytes = foregroundBytes = 0; mCycle.setVisible(false); mCycle.setHasCycles(false); } else { } else { mCycle.setVisible(true); mCycle.setHasCycles(true); final NetworkCycleDataForUid data = mUsageData.get(position); final NetworkCycleDataForUid data = mUsageData.get(position); backgroundBytes = data.getBackgroudUsage(); backgroundBytes = data.getBackgroudUsage(); foregroundBytes = data.getForegroudUsage(); foregroundBytes = data.getForegroudUsage(); Loading @@ -335,10 +347,8 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC return false; return false; } } @Override @VisibleForTesting public void onViewCreated(View view, Bundle savedInstanceState) { void addEntityHeader() { super.onViewCreated(view, savedInstanceState); String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null; String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null; int uid = 0; int uid = 0; if (pkg != null) { if (pkg != null) { Loading src/com/android/settings/datausage/CycleAdapter.java +6 −135 Original line number Original line Diff line number Diff line Loading @@ -13,24 +13,13 @@ */ */ package com.android.settings.datausage; package com.android.settings.datausage; import android.annotation.NonNull; import android.app.usage.NetworkStats; import android.content.Context; 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 android.widget.AdapterView; import com.android.net.module.util.NetworkStatsUtils; import com.android.settings.Utils; import com.android.settings.Utils; import com.android.settingslib.net.ChartData; import com.android.settingslib.net.NetworkCycleData; import com.android.settingslib.net.NetworkCycleData; import com.android.settingslib.widget.SettingsSpinnerAdapter; import com.android.settingslib.widget.SettingsSpinnerAdapter; import java.time.ZonedDateTime; import java.util.Iterator; import java.util.List; import java.util.List; import java.util.Objects; import java.util.Objects; Loading @@ -45,7 +34,6 @@ public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem> mSpinner = spinner; mSpinner = spinner; mListener = listener; mListener = listener; mSpinner.setAdapter(this); mSpinner.setAdapter(this); mSpinner.setOnItemSelectedListener(mListener); } } /** /** Loading @@ -65,128 +53,14 @@ public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem> return 0; return 0; } } protected static long getTotalBytesForTimeRange(List<NetworkStats.Bucket> stats, void setInitialCycleList(List<Long> cycles, long selectedCycle) { Range<Long> range) { long bytes = 0L; for (NetworkStats.Bucket bucket : stats) { final Range<Long> 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<Long> 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<NetworkStats.Bucket> 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<NetworkStats.Bucket>} 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(); clear(); clear(); for (int i = 0; i < cycles.size() - 1; i++) { final Context context = getContext(); add(new CycleAdapter.CycleItem(getContext(), cycles.get(i + 1), cycles.get(i))); if (cycles.get(i) == selectedCycle) { long historyStart; mSpinner.setSelection(i); long historyEnd; try { final Range<Long> 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<Pair<ZonedDateTime, ZonedDateTime>> it = NetworkPolicyManager .cycleIterator(policy); while (it.hasNext()) { final Pair<ZonedDateTime, ZonedDateTime> 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; } } } 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; } } /** /** Loading @@ -194,6 +68,7 @@ public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem> * updating the inspection range on chartData. * updating the inspection range on chartData. */ */ public boolean updateCycleList(List<? extends NetworkCycleData> cycleData) { public boolean updateCycleList(List<? extends NetworkCycleData> cycleData) { 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) mSpinner.getSelectedItem(); mSpinner.getSelectedItem(); Loading Loading @@ -228,10 +103,6 @@ public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem> public long start; public long start; public long end; public long end; public CycleItem(CharSequence label) { this.label = label; } public CycleItem(Context context, long start, long end) { public CycleItem(Context context, long start, long end) { this.label = Utils.formatDateRange(context, start, end); this.label = Utils.formatDateRange(context, start, end); this.start = start; this.start = start; Loading src/com/android/settings/datausage/SpinnerPreference.java +14 −0 Original line number Original line Diff line number Diff line Loading @@ -31,6 +31,8 @@ public class SpinnerPreference extends Preference implements CycleAdapter.Spinne private AdapterView.OnItemSelectedListener mListener; private AdapterView.OnItemSelectedListener mListener; private Object mCurrentObject; private Object mCurrentObject; private int mPosition; private int mPosition; private View mItemView; private boolean mItemViewVisible = false; public SpinnerPreference(Context context, AttributeSet attrs) { public SpinnerPreference(Context context, AttributeSet attrs) { super(context, attrs); super(context, attrs); Loading Loading @@ -63,12 +65,24 @@ public class SpinnerPreference extends Preference implements CycleAdapter.Spinne @Override @Override public void onBindViewHolder(PreferenceViewHolder holder) { public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); super.onBindViewHolder(holder); mItemView = holder.itemView; mItemView.setVisibility(mItemViewVisible ? View.VISIBLE : View.INVISIBLE); Spinner spinner = (Spinner) holder.findViewById(R.id.cycles_spinner); Spinner spinner = (Spinner) holder.findViewById(R.id.cycles_spinner); spinner.setAdapter(mAdapter); spinner.setAdapter(mAdapter); spinner.setSelection(mPosition); spinner.setSelection(mPosition); spinner.setOnItemSelectedListener(mOnSelectedListener); spinner.setOnItemSelectedListener(mOnSelectedListener); } } void setHasCycles(boolean hasData) { setVisible(hasData); if (hasData) { mItemViewVisible = true; if (mItemView != null) { mItemView.setVisibility(View.VISIBLE); } } } @Override @Override protected void performClick(View view) { protected void performClick(View view) { view.findViewById(R.id.cycles_spinner).performClick(); view.findViewById(R.id.cycles_spinner).performClick(); Loading tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java +8 −12 Original line number Original line Diff line number Diff line Loading @@ -43,7 +43,6 @@ import android.os.Process; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager; import android.text.format.DateUtils; import android.text.format.DateUtils; import android.util.ArraySet; import android.util.ArraySet; import android.view.View; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity; import androidx.preference.Preference; import androidx.preference.Preference; Loading Loading @@ -96,6 +95,10 @@ public class AppDataUsageTest { @Before @Before public void setUp() { public void setUp() { MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this); ShadowEntityHeaderController.setUseMock(mHeaderController); when(mHeaderController.setRecyclerView(any(), any())).thenReturn(mHeaderController); when(mHeaderController.setUid(anyInt())).thenReturn(mHeaderController); } } @After @After Loading Loading @@ -163,10 +166,6 @@ public class AppDataUsageTest { @Test @Test public void bindAppHeader_allWorkApps_shouldNotShowAppInfoLink() { 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()); mFragment = spy(new AppDataUsage()); when(mFragment.getPreferenceManager()) when(mFragment.getPreferenceManager()) Loading @@ -174,7 +173,7 @@ public class AppDataUsageTest { doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); ReflectionHelpers.setField(mFragment, "mAppItem", mock(AppItem.class)); ReflectionHelpers.setField(mFragment, "mAppItem", mock(AppItem.class)); mFragment.onViewCreated(new View(RuntimeEnvironment.application), new Bundle()); mFragment.addEntityHeader(); verify(mHeaderController).setHasAppInfoLink(false); verify(mHeaderController).setHasAppInfoLink(false); } } Loading @@ -196,16 +195,13 @@ public class AppDataUsageTest { when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())) when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())) .thenReturn(fakeUserId); .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(mHeaderController.setHasAppInfoLink(anyBoolean())).thenReturn(mHeaderController); when(mFragment.getPreferenceManager()) when(mFragment.getPreferenceManager()) .thenReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS)); .thenReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS)); doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); mFragment.onViewCreated(new View(RuntimeEnvironment.application), new Bundle()); mFragment.addEntityHeader(); verify(mHeaderController).setHasAppInfoLink(true); verify(mHeaderController).setHasAppInfoLink(true); verify(mHeaderController).setUid(fakeUserId); verify(mHeaderController).setUid(fakeUserId); Loading Loading @@ -268,7 +264,7 @@ public class AppDataUsageTest { mFragment.bindData(0 /* position */); mFragment.bindData(0 /* position */); verify(cycle).setVisible(false); verify(cycle).setHasCycles(false); } } @Test @Test Loading @@ -293,7 +289,7 @@ public class AppDataUsageTest { mFragment.bindData(0 /* position */); mFragment.bindData(0 /* position */); verify(cycle).setVisible(true); verify(cycle).setHasCycles(true); verify(totalPref).setSummary( verify(totalPref).setSummary( DataUsageUtils.formatDataUsage(context, backgroundBytes + foregroundBytes)); DataUsageUtils.formatDataUsage(context, backgroundBytes + foregroundBytes)); verify(backgroundPref).setSummary(DataUsageUtils.formatDataUsage(context, backgroundBytes)); verify(backgroundPref).setSummary(DataUsageUtils.formatDataUsage(context, backgroundBytes)); Loading Loading
res/xml/app_data_usage.xml +7 −5 Original line number Original line Diff line number Diff line Loading @@ -21,8 +21,7 @@ android:title="@string/data_usage_app_summary_title"> android:title="@string/data_usage_app_summary_title"> <com.android.settings.datausage.SpinnerPreference <com.android.settings.datausage.SpinnerPreference android:key="cycle" android:key="cycle" /> settings:isPreferenceVisible="false" /> <PreferenceCategory <PreferenceCategory android:key="app_data_usage_summary_category"> android:key="app_data_usage_summary_category"> Loading @@ -31,19 +30,22 @@ android:key="total_usage" android:key="total_usage" android:title="@string/total_size_label" android:title="@string/total_size_label" android:selectable="false" android:selectable="false" android:layout="@layout/horizontal_preference" /> android:layout="@layout/horizontal_preference" android:summary="@string/summary_placeholder" /> <Preference <Preference android:key="foreground_usage" android:key="foreground_usage" android:title="@string/data_usage_label_foreground" android:title="@string/data_usage_label_foreground" android:selectable="false" android:selectable="false" android:layout="@layout/horizontal_preference" /> android:layout="@layout/horizontal_preference" android:summary="@string/summary_placeholder" /> <Preference <Preference android:key="background_usage" android:key="background_usage" android:title="@string/data_usage_label_background" android:title="@string/data_usage_label_background" android:selectable="false" android:selectable="false" android:layout="@layout/horizontal_preference" /> android:layout="@layout/horizontal_preference" android:summary="@string/summary_placeholder" /> </PreferenceCategory> </PreferenceCategory> Loading
src/com/android/settings/datausage/AppDataUsage.java +18 −8 Original line number Original line Diff line number Diff line Loading @@ -144,8 +144,7 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE); mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE); mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE); mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE); mCycle = findPreference(KEY_CYCLE); initCycle(); mCycleAdapter = new CycleAdapter(mContext, mCycle, mCycleListener); final UidDetailProvider uidDetailProvider = getUidDetailProvider(); final UidDetailProvider uidDetailProvider = getUidDetailProvider(); Loading Loading @@ -211,6 +210,8 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC removePreference(KEY_RESTRICT_BACKGROUND); removePreference(KEY_RESTRICT_BACKGROUND); removePreference(KEY_APP_LIST); removePreference(KEY_APP_LIST); } } addEntityHeader(); } } @Override @Override Loading Loading @@ -276,6 +277,17 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC return new UidDetailProvider(mContext); 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) { private void updatePrefs(boolean restrictBackground, boolean unrestrictData) { final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfMeteredDataRestricted( final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfMeteredDataRestricted( mContext, mPackageName, UserHandle.getUserId(mAppItem.key)); mContext, mPackageName, UserHandle.getUserId(mAppItem.key)); Loading Loading @@ -308,9 +320,9 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC final long backgroundBytes, foregroundBytes; final long backgroundBytes, foregroundBytes; if (mUsageData == null || position >= mUsageData.size()) { if (mUsageData == null || position >= mUsageData.size()) { backgroundBytes = foregroundBytes = 0; backgroundBytes = foregroundBytes = 0; mCycle.setVisible(false); mCycle.setHasCycles(false); } else { } else { mCycle.setVisible(true); mCycle.setHasCycles(true); final NetworkCycleDataForUid data = mUsageData.get(position); final NetworkCycleDataForUid data = mUsageData.get(position); backgroundBytes = data.getBackgroudUsage(); backgroundBytes = data.getBackgroudUsage(); foregroundBytes = data.getForegroudUsage(); foregroundBytes = data.getForegroudUsage(); Loading @@ -335,10 +347,8 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC return false; return false; } } @Override @VisibleForTesting public void onViewCreated(View view, Bundle savedInstanceState) { void addEntityHeader() { super.onViewCreated(view, savedInstanceState); String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null; String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null; int uid = 0; int uid = 0; if (pkg != null) { if (pkg != null) { Loading
src/com/android/settings/datausage/CycleAdapter.java +6 −135 Original line number Original line Diff line number Diff line Loading @@ -13,24 +13,13 @@ */ */ package com.android.settings.datausage; package com.android.settings.datausage; import android.annotation.NonNull; import android.app.usage.NetworkStats; import android.content.Context; 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 android.widget.AdapterView; import com.android.net.module.util.NetworkStatsUtils; import com.android.settings.Utils; import com.android.settings.Utils; import com.android.settingslib.net.ChartData; import com.android.settingslib.net.NetworkCycleData; import com.android.settingslib.net.NetworkCycleData; import com.android.settingslib.widget.SettingsSpinnerAdapter; import com.android.settingslib.widget.SettingsSpinnerAdapter; import java.time.ZonedDateTime; import java.util.Iterator; import java.util.List; import java.util.List; import java.util.Objects; import java.util.Objects; Loading @@ -45,7 +34,6 @@ public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem> mSpinner = spinner; mSpinner = spinner; mListener = listener; mListener = listener; mSpinner.setAdapter(this); mSpinner.setAdapter(this); mSpinner.setOnItemSelectedListener(mListener); } } /** /** Loading @@ -65,128 +53,14 @@ public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem> return 0; return 0; } } protected static long getTotalBytesForTimeRange(List<NetworkStats.Bucket> stats, void setInitialCycleList(List<Long> cycles, long selectedCycle) { Range<Long> range) { long bytes = 0L; for (NetworkStats.Bucket bucket : stats) { final Range<Long> 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<Long> 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<NetworkStats.Bucket> 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<NetworkStats.Bucket>} 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(); clear(); clear(); for (int i = 0; i < cycles.size() - 1; i++) { final Context context = getContext(); add(new CycleAdapter.CycleItem(getContext(), cycles.get(i + 1), cycles.get(i))); if (cycles.get(i) == selectedCycle) { long historyStart; mSpinner.setSelection(i); long historyEnd; try { final Range<Long> 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<Pair<ZonedDateTime, ZonedDateTime>> it = NetworkPolicyManager .cycleIterator(policy); while (it.hasNext()) { final Pair<ZonedDateTime, ZonedDateTime> 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; } } } 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; } } /** /** Loading @@ -194,6 +68,7 @@ public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem> * updating the inspection range on chartData. * updating the inspection range on chartData. */ */ public boolean updateCycleList(List<? extends NetworkCycleData> cycleData) { public boolean updateCycleList(List<? extends NetworkCycleData> cycleData) { 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) mSpinner.getSelectedItem(); mSpinner.getSelectedItem(); Loading Loading @@ -228,10 +103,6 @@ public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem> public long start; public long start; public long end; public long end; public CycleItem(CharSequence label) { this.label = label; } public CycleItem(Context context, long start, long end) { public CycleItem(Context context, long start, long end) { this.label = Utils.formatDateRange(context, start, end); this.label = Utils.formatDateRange(context, start, end); this.start = start; this.start = start; Loading
src/com/android/settings/datausage/SpinnerPreference.java +14 −0 Original line number Original line Diff line number Diff line Loading @@ -31,6 +31,8 @@ public class SpinnerPreference extends Preference implements CycleAdapter.Spinne private AdapterView.OnItemSelectedListener mListener; private AdapterView.OnItemSelectedListener mListener; private Object mCurrentObject; private Object mCurrentObject; private int mPosition; private int mPosition; private View mItemView; private boolean mItemViewVisible = false; public SpinnerPreference(Context context, AttributeSet attrs) { public SpinnerPreference(Context context, AttributeSet attrs) { super(context, attrs); super(context, attrs); Loading Loading @@ -63,12 +65,24 @@ public class SpinnerPreference extends Preference implements CycleAdapter.Spinne @Override @Override public void onBindViewHolder(PreferenceViewHolder holder) { public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); super.onBindViewHolder(holder); mItemView = holder.itemView; mItemView.setVisibility(mItemViewVisible ? View.VISIBLE : View.INVISIBLE); Spinner spinner = (Spinner) holder.findViewById(R.id.cycles_spinner); Spinner spinner = (Spinner) holder.findViewById(R.id.cycles_spinner); spinner.setAdapter(mAdapter); spinner.setAdapter(mAdapter); spinner.setSelection(mPosition); spinner.setSelection(mPosition); spinner.setOnItemSelectedListener(mOnSelectedListener); spinner.setOnItemSelectedListener(mOnSelectedListener); } } void setHasCycles(boolean hasData) { setVisible(hasData); if (hasData) { mItemViewVisible = true; if (mItemView != null) { mItemView.setVisibility(View.VISIBLE); } } } @Override @Override protected void performClick(View view) { protected void performClick(View view) { view.findViewById(R.id.cycles_spinner).performClick(); view.findViewById(R.id.cycles_spinner).performClick(); Loading
tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java +8 −12 Original line number Original line Diff line number Diff line Loading @@ -43,7 +43,6 @@ import android.os.Process; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager; import android.text.format.DateUtils; import android.text.format.DateUtils; import android.util.ArraySet; import android.util.ArraySet; import android.view.View; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity; import androidx.preference.Preference; import androidx.preference.Preference; Loading Loading @@ -96,6 +95,10 @@ public class AppDataUsageTest { @Before @Before public void setUp() { public void setUp() { MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this); ShadowEntityHeaderController.setUseMock(mHeaderController); when(mHeaderController.setRecyclerView(any(), any())).thenReturn(mHeaderController); when(mHeaderController.setUid(anyInt())).thenReturn(mHeaderController); } } @After @After Loading Loading @@ -163,10 +166,6 @@ public class AppDataUsageTest { @Test @Test public void bindAppHeader_allWorkApps_shouldNotShowAppInfoLink() { 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()); mFragment = spy(new AppDataUsage()); when(mFragment.getPreferenceManager()) when(mFragment.getPreferenceManager()) Loading @@ -174,7 +173,7 @@ public class AppDataUsageTest { doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); ReflectionHelpers.setField(mFragment, "mAppItem", mock(AppItem.class)); ReflectionHelpers.setField(mFragment, "mAppItem", mock(AppItem.class)); mFragment.onViewCreated(new View(RuntimeEnvironment.application), new Bundle()); mFragment.addEntityHeader(); verify(mHeaderController).setHasAppInfoLink(false); verify(mHeaderController).setHasAppInfoLink(false); } } Loading @@ -196,16 +195,13 @@ public class AppDataUsageTest { when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())) when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())) .thenReturn(fakeUserId); .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(mHeaderController.setHasAppInfoLink(anyBoolean())).thenReturn(mHeaderController); when(mFragment.getPreferenceManager()) when(mFragment.getPreferenceManager()) .thenReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS)); .thenReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS)); doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); mFragment.onViewCreated(new View(RuntimeEnvironment.application), new Bundle()); mFragment.addEntityHeader(); verify(mHeaderController).setHasAppInfoLink(true); verify(mHeaderController).setHasAppInfoLink(true); verify(mHeaderController).setUid(fakeUserId); verify(mHeaderController).setUid(fakeUserId); Loading Loading @@ -268,7 +264,7 @@ public class AppDataUsageTest { mFragment.bindData(0 /* position */); mFragment.bindData(0 /* position */); verify(cycle).setVisible(false); verify(cycle).setHasCycles(false); } } @Test @Test Loading @@ -293,7 +289,7 @@ public class AppDataUsageTest { mFragment.bindData(0 /* position */); mFragment.bindData(0 /* position */); verify(cycle).setVisible(true); verify(cycle).setHasCycles(true); verify(totalPref).setSummary( verify(totalPref).setSummary( DataUsageUtils.formatDataUsage(context, backgroundBytes + foregroundBytes)); DataUsageUtils.formatDataUsage(context, backgroundBytes + foregroundBytes)); verify(backgroundPref).setSummary(DataUsageUtils.formatDataUsage(context, backgroundBytes)); verify(backgroundPref).setSummary(DataUsageUtils.formatDataUsage(context, backgroundBytes)); Loading