Improve the latency of DataUsageList
Up to 30x faster. Currently it load the usage detail for every day at the beginning, so it's quite slow. To fix, - Not load the usage detail for every day at the beginning - Load only the cycles first - And only load the daily detail for the selected month Fix: 290856342 Test: manual - on DataUsageList (cell & wifi) Test: unit tests Change-Id: Ie18fa68f801743389bd6b6a28e236dcf1fea00e4
This commit is contained in:
@@ -22,7 +22,9 @@
|
|||||||
android:title="@string/summary_placeholder">
|
android:title="@string/summary_placeholder">
|
||||||
|
|
||||||
<com.android.settings.datausage.ChartDataUsagePreference
|
<com.android.settings.datausage.ChartDataUsagePreference
|
||||||
android:key="chart_data" />
|
android:key="chart_data"
|
||||||
|
settings:controller="com.android.settings.datausage.ChartDataUsagePreferenceController"
|
||||||
|
/>
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="non_carrier_data_usage_warning"
|
android:key="non_carrier_data_usage_warning"
|
||||||
|
@@ -31,6 +31,7 @@ import android.telephony.SubscriptionManager;
|
|||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import android.util.IconDrawableFactory;
|
import android.util.IconDrawableFactory;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Range;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
|
|
||||||
@@ -472,7 +473,9 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
|
|||||||
List<NetworkCycleDataForUid> data) {
|
List<NetworkCycleDataForUid> data) {
|
||||||
mUsageData = data;
|
mUsageData = data;
|
||||||
mCycle.setOnItemSelectedListener(mCycleListener);
|
mCycle.setOnItemSelectedListener(mCycleListener);
|
||||||
mCycleAdapter.updateCycleList(data);
|
mCycleAdapter.updateCycleList(data.stream()
|
||||||
|
.map(cycle -> new Range<>(cycle.getStartTime(), cycle.getEndTime()))
|
||||||
|
.toList());
|
||||||
if (mSelectedCycle > 0L) {
|
if (mSelectedCycle > 0L) {
|
||||||
final int numCycles = data.size();
|
final int numCycles = data.size();
|
||||||
int position = 0;
|
int position = 0;
|
||||||
|
@@ -26,15 +26,17 @@ import android.util.AttributeSet;
|
|||||||
import android.util.DataUnit;
|
import android.util.DataUnit;
|
||||||
import android.util.SparseIntArray;
|
import android.util.SparseIntArray;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceViewHolder;
|
import androidx.preference.PreferenceViewHolder;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.Utils;
|
import com.android.settings.Utils;
|
||||||
|
import com.android.settings.datausage.lib.NetworkCycleChartData;
|
||||||
|
import com.android.settings.datausage.lib.NetworkUsageData;
|
||||||
import com.android.settings.widget.UsageView;
|
import com.android.settings.widget.UsageView;
|
||||||
import com.android.settingslib.net.NetworkCycleChartData;
|
|
||||||
import com.android.settingslib.net.NetworkCycleData;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@@ -51,8 +53,8 @@ public class ChartDataUsagePreference extends Preference {
|
|||||||
private final int mWarningColor;
|
private final int mWarningColor;
|
||||||
private final int mLimitColor;
|
private final int mLimitColor;
|
||||||
|
|
||||||
private Resources mResources;
|
private final Resources mResources;
|
||||||
private NetworkPolicy mPolicy;
|
@Nullable private NetworkPolicy mPolicy;
|
||||||
private long mStart;
|
private long mStart;
|
||||||
private long mEnd;
|
private long mEnd;
|
||||||
private NetworkCycleChartData mNetworkCycleChartData;
|
private NetworkCycleChartData mNetworkCycleChartData;
|
||||||
@@ -67,18 +69,16 @@ public class ChartDataUsagePreference extends Preference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
|
||||||
super.onBindViewHolder(holder);
|
super.onBindViewHolder(holder);
|
||||||
final UsageView chart = (UsageView) holder.findViewById(R.id.data_usage);
|
final UsageView chart = holder.itemView.requireViewById(R.id.data_usage);
|
||||||
if (mNetworkCycleChartData == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int top = getTop();
|
final int top = getTop();
|
||||||
chart.clearPaths();
|
chart.clearPaths();
|
||||||
chart.configureGraph(toInt(mEnd - mStart), top);
|
chart.configureGraph(toInt(mEnd - mStart), top);
|
||||||
calcPoints(chart, mNetworkCycleChartData.getUsageBuckets());
|
if (mNetworkCycleChartData != null) {
|
||||||
setupContentDescription(chart, mNetworkCycleChartData.getUsageBuckets());
|
calcPoints(chart, mNetworkCycleChartData.getDailyUsage());
|
||||||
|
setupContentDescription(chart, mNetworkCycleChartData.getDailyUsage());
|
||||||
|
}
|
||||||
chart.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),
|
||||||
@@ -88,23 +88,21 @@ public class ChartDataUsagePreference extends Preference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getTop() {
|
public int getTop() {
|
||||||
final long totalData = mNetworkCycleChartData.getTotalUsage();
|
final long totalData =
|
||||||
|
mNetworkCycleChartData != null ? mNetworkCycleChartData.getTotal().getUsage() : 0;
|
||||||
final long policyMax =
|
final long policyMax =
|
||||||
mPolicy != null ? Math.max(mPolicy.limitBytes, mPolicy.warningBytes) : 0;
|
mPolicy != null ? Math.max(mPolicy.limitBytes, mPolicy.warningBytes) : 0;
|
||||||
return (int) (Math.max(totalData, policyMax) / RESOLUTION);
|
return (int) (Math.max(totalData, policyMax) / RESOLUTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void calcPoints(UsageView chart, List<NetworkCycleData> usageSummary) {
|
void calcPoints(UsageView chart, @NonNull List<NetworkUsageData> usageSummary) {
|
||||||
if (usageSummary == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final SparseIntArray points = new SparseIntArray();
|
final SparseIntArray points = new SparseIntArray();
|
||||||
points.put(0, 0);
|
points.put(0, 0);
|
||||||
|
|
||||||
final long now = System.currentTimeMillis();
|
final long now = System.currentTimeMillis();
|
||||||
long totalData = 0;
|
long totalData = 0;
|
||||||
for (NetworkCycleData data : usageSummary) {
|
for (NetworkUsageData data : usageSummary) {
|
||||||
final long startTime = data.getStartTime();
|
final long startTime = data.getStartTime();
|
||||||
if (startTime > now) {
|
if (startTime > now) {
|
||||||
break;
|
break;
|
||||||
@@ -112,7 +110,7 @@ public class ChartDataUsagePreference extends Preference {
|
|||||||
final long endTime = data.getEndTime();
|
final long endTime = data.getEndTime();
|
||||||
|
|
||||||
// increment by current bucket total
|
// increment by current bucket total
|
||||||
totalData += data.getTotalUsage();
|
totalData += data.getUsage();
|
||||||
|
|
||||||
if (points.size() == 1) {
|
if (points.size() == 1) {
|
||||||
points.put(toInt(startTime - mStart) - 1, -1);
|
points.put(toInt(startTime - mStart) - 1, -1);
|
||||||
@@ -125,7 +123,8 @@ public class ChartDataUsagePreference extends Preference {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupContentDescription(UsageView chart, List<NetworkCycleData> usageSummary) {
|
private void setupContentDescription(
|
||||||
|
UsageView chart, @NonNull List<NetworkUsageData> usageSummary) {
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
final StringBuilder contentDescription = new StringBuilder();
|
final StringBuilder contentDescription = new StringBuilder();
|
||||||
final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH;
|
final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH;
|
||||||
@@ -137,7 +136,7 @@ public class ChartDataUsagePreference extends Preference {
|
|||||||
.getString(R.string.data_usage_chart_brief_content_description, startDate, endDate);
|
.getString(R.string.data_usage_chart_brief_content_description, startDate, endDate);
|
||||||
contentDescription.append(briefContentDescription);
|
contentDescription.append(briefContentDescription);
|
||||||
|
|
||||||
if (usageSummary == null || usageSummary.isEmpty()) {
|
if (usageSummary.isEmpty()) {
|
||||||
final String noDataContentDescription = mResources
|
final String noDataContentDescription = mResources
|
||||||
.getString(R.string.data_usage_chart_no_data_content_description);
|
.getString(R.string.data_usage_chart_no_data_content_description);
|
||||||
contentDescription.append(noDataContentDescription);
|
contentDescription.append(noDataContentDescription);
|
||||||
@@ -170,17 +169,17 @@ public class ChartDataUsagePreference extends Preference {
|
|||||||
* Collect the date of the same percentage, e.g., Aug 2 to Aug 22: 0%; Aug 23: 2%.
|
* Collect the date of the same percentage, e.g., Aug 2 to Aug 22: 0%; Aug 23: 2%.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
List<DataUsageSummaryNode> getDensedStatsData(List<NetworkCycleData> usageSummary) {
|
List<DataUsageSummaryNode> getDensedStatsData(@NonNull List<NetworkUsageData> usageSummary) {
|
||||||
final List<DataUsageSummaryNode> dataUsageSummaryNodes = new ArrayList<>();
|
final List<DataUsageSummaryNode> dataUsageSummaryNodes = new ArrayList<>();
|
||||||
final long overallDataUsage = Math.max(1L, usageSummary.stream()
|
final long overallDataUsage = Math.max(1L, usageSummary.stream()
|
||||||
.mapToLong(NetworkCycleData::getTotalUsage).sum());
|
.mapToLong(NetworkUsageData::getUsage).sum());
|
||||||
long cumulatedDataUsage = 0L;
|
long cumulatedDataUsage = 0L;
|
||||||
int cumulatedDataUsagePercentage = 0;
|
|
||||||
|
|
||||||
// Collect List of DataUsageSummaryNode for data usage percentage information.
|
// Collect List of DataUsageSummaryNode for data usage percentage information.
|
||||||
for (NetworkCycleData data : usageSummary) {
|
for (NetworkUsageData data : usageSummary) {
|
||||||
cumulatedDataUsage += data.getTotalUsage();
|
cumulatedDataUsage += data.getUsage();
|
||||||
cumulatedDataUsagePercentage = (int) ((cumulatedDataUsage * 100) / overallDataUsage);
|
int cumulatedDataUsagePercentage =
|
||||||
|
(int) ((cumulatedDataUsage * 100) / overallDataUsage);
|
||||||
|
|
||||||
final DataUsageSummaryNode node = new DataUsageSummaryNode(data.getStartTime(),
|
final DataUsageSummaryNode node = new DataUsageSummaryNode(data.getStartTime(),
|
||||||
data.getEndTime(), cumulatedDataUsagePercentage);
|
data.getEndTime(), cumulatedDataUsagePercentage);
|
||||||
@@ -268,8 +267,9 @@ public class ChartDataUsagePreference extends Preference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) {
|
if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) {
|
||||||
chart.setDividerLoc((int) (policy.warningBytes / RESOLUTION));
|
int dividerLoc = (int) (policy.warningBytes / RESOLUTION);
|
||||||
float weight = policy.warningBytes / RESOLUTION / (float) top;
|
chart.setDividerLoc(dividerLoc);
|
||||||
|
float weight = dividerLoc / (float) top;
|
||||||
float above = 1 - weight;
|
float above = 1 - weight;
|
||||||
chart.setSideLabelWeights(above, weight);
|
chart.setSideLabelWeights(above, weight);
|
||||||
middleVisibility = mWarningColor;
|
middleVisibility = mWarningColor;
|
||||||
@@ -289,15 +289,21 @@ 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 setNetworkPolicy(NetworkPolicy policy) {
|
/** Sets network policy. */
|
||||||
|
public void setNetworkPolicy(@Nullable NetworkPolicy policy) {
|
||||||
mPolicy = policy;
|
mPolicy = policy;
|
||||||
notifyChanged();
|
notifyChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sets time. */
|
||||||
|
public void setTime(long start, long end) {
|
||||||
|
mStart = start;
|
||||||
|
mEnd = end;
|
||||||
|
notifyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
public void setNetworkCycleData(NetworkCycleChartData data) {
|
public void setNetworkCycleData(NetworkCycleChartData data) {
|
||||||
mNetworkCycleChartData = data;
|
mNetworkCycleChartData = data;
|
||||||
mStart = data.getStartTime();
|
|
||||||
mEnd = data.getEndTime();
|
|
||||||
notifyChanged();
|
notifyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.datausage
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.NetworkTemplate
|
||||||
|
import androidx.annotation.OpenForTesting
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
import com.android.settings.core.BasePreferenceController
|
||||||
|
import com.android.settings.datausage.lib.INetworkCycleDataRepository
|
||||||
|
import com.android.settings.datausage.lib.NetworkCycleDataRepository
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@OpenForTesting
|
||||||
|
open class ChartDataUsagePreferenceController(context: Context, preferenceKey: String) :
|
||||||
|
BasePreferenceController(context, preferenceKey) {
|
||||||
|
|
||||||
|
private lateinit var repository: INetworkCycleDataRepository
|
||||||
|
private lateinit var preference: ChartDataUsagePreference
|
||||||
|
private lateinit var lifecycleScope: LifecycleCoroutineScope
|
||||||
|
|
||||||
|
open fun init(template: NetworkTemplate) {
|
||||||
|
this.repository = NetworkCycleDataRepository(mContext, template)
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
fun init(repository: INetworkCycleDataRepository) {
|
||||||
|
this.repository = repository
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAvailabilityStatus() = AVAILABLE
|
||||||
|
|
||||||
|
override fun displayPreference(screen: PreferenceScreen) {
|
||||||
|
super.displayPreference(screen)
|
||||||
|
preference = screen.findPreference(preferenceKey)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
|
||||||
|
lifecycleScope = viewLifecycleOwner.lifecycleScope
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether billing cycle modifiable.
|
||||||
|
*
|
||||||
|
* Don't bind warning / limit sweeps if not modifiable.
|
||||||
|
*/
|
||||||
|
open fun setBillingCycleModifiable(isModifiable: Boolean) {
|
||||||
|
preference.setNetworkPolicy(
|
||||||
|
if (isModifiable) repository.getPolicy() else null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(startTime: Long, endTime: Long) {
|
||||||
|
preference.setTime(startTime, endTime)
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val chartData = withContext(Dispatchers.Default) {
|
||||||
|
repository.querySummary(startTime, endTime)
|
||||||
|
}
|
||||||
|
preference.setNetworkCycleData(chartData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -14,9 +14,9 @@
|
|||||||
package com.android.settings.datausage;
|
package com.android.settings.datausage;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.util.Range;
|
||||||
|
|
||||||
import com.android.settings.Utils;
|
import com.android.settings.Utils;
|
||||||
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;
|
||||||
@@ -62,15 +62,15 @@ 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 void updateCycleList(List<? extends NetworkCycleData> cycleData) {
|
public void updateCycleList(List<Range<Long>> cycleData) {
|
||||||
// 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();
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
for (NetworkCycleData data : cycleData) {
|
for (Range<Long> cycle : cycleData) {
|
||||||
add(new CycleAdapter.CycleItem(context, data.getStartTime(), data.getEndTime()));
|
add(new CycleAdapter.CycleItem(context, cycle.getLower(), cycle.getUpper()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// force pick the current cycle (first item)
|
// force pick the current cycle (first item)
|
||||||
|
@@ -1,358 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
|
||||||
* except in compliance with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
|
||||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the specific language governing
|
|
||||||
* permissions and limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.datausage;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.settings.SettingsEnums;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.NetworkPolicy;
|
|
||||||
import android.net.NetworkTemplate;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.UserManager;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import android.telephony.SubscriptionManager;
|
|
||||||
import android.util.EventLog;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
import androidx.lifecycle.Lifecycle;
|
|
||||||
import androidx.loader.app.LoaderManager.LoaderCallbacks;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.datausage.lib.BillingCycleRepository;
|
|
||||||
import com.android.settings.network.MobileDataEnabledListener;
|
|
||||||
import com.android.settings.network.MobileNetworkRepository;
|
|
||||||
import com.android.settings.widget.LoadingViewController;
|
|
||||||
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
|
|
||||||
import com.android.settingslib.net.NetworkCycleChartData;
|
|
||||||
import com.android.settingslib.net.NetworkCycleChartDataLoader;
|
|
||||||
import com.android.settingslib.utils.ThreadUtils;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import kotlin.Unit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Panel showing data usage history across various networks, including options
|
|
||||||
* to inspect based on usage cycle and control through {@link NetworkPolicy}.
|
|
||||||
*/
|
|
||||||
public class DataUsageList extends DataUsageBaseFragment
|
|
||||||
implements MobileDataEnabledListener.Client {
|
|
||||||
|
|
||||||
static final String EXTRA_SUB_ID = "sub_id";
|
|
||||||
static final String EXTRA_NETWORK_TEMPLATE = "network_template";
|
|
||||||
|
|
||||||
private static final String TAG = "DataUsageList";
|
|
||||||
private static final boolean LOGD = false;
|
|
||||||
|
|
||||||
private static final String KEY_USAGE_AMOUNT = "usage_amount";
|
|
||||||
private static final String KEY_CHART_DATA = "chart_data";
|
|
||||||
private static final String KEY_TEMPLATE = "template";
|
|
||||||
private static final String KEY_APP = "app";
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static final int LOADER_CHART_DATA = 2;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
MobileDataEnabledListener mDataStateListener;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
NetworkTemplate mTemplate;
|
|
||||||
@VisibleForTesting
|
|
||||||
int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
|
||||||
@VisibleForTesting
|
|
||||||
LoadingViewController mLoadingViewController;
|
|
||||||
|
|
||||||
private ChartDataUsagePreference mChart;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private List<NetworkCycleChartData> mCycleData;
|
|
||||||
|
|
||||||
// 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 Preference mUsageAmount;
|
|
||||||
private MobileNetworkRepository mMobileNetworkRepository;
|
|
||||||
private SubscriptionInfoEntity mSubscriptionInfoEntity;
|
|
||||||
private DataUsageListAppsController mDataUsageListAppsController;
|
|
||||||
private BillingCycleRepository mBillingCycleRepository;
|
|
||||||
@VisibleForTesting
|
|
||||||
DataUsageListHeaderController mDataUsageListHeaderController;
|
|
||||||
|
|
||||||
private boolean mIsBillingCycleModifiable = false;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMetricsCategory() {
|
|
||||||
return SettingsEnums.DATA_USAGE_LIST;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
if (isGuestUser(getContext())) {
|
|
||||||
Log.e(TAG, "This setting isn't available for guest user");
|
|
||||||
EventLog.writeEvent(0x534e4554, "262741858", -1 /* UID */, "Guest user");
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
mBillingCycleRepository = createBillingCycleRepository();
|
|
||||||
if (!mBillingCycleRepository.isBandwidthControlEnabled()) {
|
|
||||||
Log.w(TAG, "No bandwidth control; leaving");
|
|
||||||
activity.finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mUsageAmount = findPreference(KEY_USAGE_AMOUNT);
|
|
||||||
mChart = findPreference(KEY_CHART_DATA);
|
|
||||||
|
|
||||||
processArgument();
|
|
||||||
if (mTemplate == null) {
|
|
||||||
Log.e(TAG, "No template; leaving");
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
updateSubscriptionInfoEntity();
|
|
||||||
mDataStateListener = new MobileDataEnabledListener(activity, this);
|
|
||||||
mDataUsageListAppsController = use(DataUsageListAppsController.class);
|
|
||||||
mDataUsageListAppsController.init(mTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
@NonNull
|
|
||||||
BillingCycleRepository createBillingCycleRepository() {
|
|
||||||
return new BillingCycleRepository(requireContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(@NonNull View v, Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(v, savedInstanceState);
|
|
||||||
|
|
||||||
mDataUsageListHeaderController = new DataUsageListHeaderController(
|
|
||||||
setPinnedHeaderView(R.layout.apps_filter_spinner),
|
|
||||||
mTemplate,
|
|
||||||
getMetricsCategory(),
|
|
||||||
(cycle, position) -> {
|
|
||||||
updateSelectedCycle(cycle, position);
|
|
||||||
return Unit.INSTANCE;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
mLoadingViewController = new LoadingViewController(
|
|
||||||
getView().findViewById(R.id.loading_container), getListView());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
mLoadingViewController.showLoadingViewDelayed();
|
|
||||||
mDataStateListener.start(mSubId);
|
|
||||||
mLastDisplayedCycle = null;
|
|
||||||
updatePolicy();
|
|
||||||
|
|
||||||
// kick off loader for network history
|
|
||||||
// TODO: consider chaining two loaders together instead of reloading
|
|
||||||
// network history when showing app detail.
|
|
||||||
getLoaderManager().restartLoader(LOADER_CHART_DATA,
|
|
||||||
buildArgs(mTemplate), mNetworkCycleDataCallbacks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
mDataStateListener.stop();
|
|
||||||
|
|
||||||
getLoaderManager().destroyLoader(LOADER_CHART_DATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getPreferenceScreenResId() {
|
|
||||||
return R.xml.data_usage_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getLogTag() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
void processArgument() {
|
|
||||||
final Bundle args = getArguments();
|
|
||||||
if (args != null) {
|
|
||||||
mSubId = args.getInt(EXTRA_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
|
|
||||||
mTemplate = args.getParcelable(EXTRA_NETWORK_TEMPLATE);
|
|
||||||
}
|
|
||||||
if (mTemplate == null && mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
|
|
||||||
final Intent intent = getIntent();
|
|
||||||
mSubId = intent.getIntExtra(Settings.EXTRA_SUB_ID,
|
|
||||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
|
|
||||||
mTemplate = intent.getParcelableExtra(Settings.EXTRA_NETWORK_TEMPLATE);
|
|
||||||
|
|
||||||
if (mTemplate == null) {
|
|
||||||
Optional<NetworkTemplate> mobileNetworkTemplateFromSim =
|
|
||||||
DataUsageUtils.getMobileNetworkTemplateFromSubId(getContext(), getIntent());
|
|
||||||
if (mobileNetworkTemplateFromSim.isPresent()) {
|
|
||||||
mTemplate = mobileNetworkTemplateFromSim.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
void updateSubscriptionInfoEntity() {
|
|
||||||
mMobileNetworkRepository = MobileNetworkRepository.getInstance(getContext());
|
|
||||||
ThreadUtils.postOnBackgroundThread(() -> {
|
|
||||||
mSubscriptionInfoEntity = mMobileNetworkRepository.getSubInfoById(
|
|
||||||
String.valueOf(mSubId));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of {@code MobileDataEnabledListener.Client}
|
|
||||||
*/
|
|
||||||
public void onMobileDataEnabledChange() {
|
|
||||||
updatePolicy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Bundle buildArgs(NetworkTemplate template) {
|
|
||||||
final Bundle args = new Bundle();
|
|
||||||
args.putParcelable(KEY_TEMPLATE, template);
|
|
||||||
args.putParcelable(KEY_APP, null);
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
|
|
||||||
* current {@link #mTemplate}.
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
void updatePolicy() {
|
|
||||||
mIsBillingCycleModifiable = isBillingCycleModifiable();
|
|
||||||
if (mIsBillingCycleModifiable) {
|
|
||||||
mChart.setNetworkPolicy(services.mPolicyEditor.getPolicy(mTemplate));
|
|
||||||
} else {
|
|
||||||
mChart.setNetworkPolicy(null); // don't bind warning / limit sweeps
|
|
||||||
}
|
|
||||||
updateConfigButtonVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
boolean isBillingCycleModifiable() {
|
|
||||||
return mBillingCycleRepository.isModifiable(mSubId)
|
|
||||||
&& SubscriptionManager.from(requireContext())
|
|
||||||
.getActiveSubscriptionInfo(mSubId) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateConfigButtonVisibility() {
|
|
||||||
mDataUsageListHeaderController.setConfigButtonVisible(
|
|
||||||
mIsBillingCycleModifiable && mCycleData != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the chart and detail data when initial loaded or selected cycle changed.
|
|
||||||
*/
|
|
||||||
private void updateSelectedCycle(CycleAdapter.CycleItem cycle, int position) {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
NetworkCycleChartData cycleChartData = mCycleData.get(position);
|
|
||||||
mChart.setNetworkCycleData(cycleChartData);
|
|
||||||
|
|
||||||
updateDetailData(cycleChartData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update details based on {@link #mChart} inspection range depending on
|
|
||||||
* current mode. Updates {@link #mAdapter} with sorted list
|
|
||||||
* of applications data usage.
|
|
||||||
*/
|
|
||||||
private void updateDetailData(NetworkCycleChartData cycleChartData) {
|
|
||||||
if (LOGD) Log.d(TAG, "updateDetailData()");
|
|
||||||
|
|
||||||
// kick off loader for detailed stats
|
|
||||||
mDataUsageListAppsController.update(
|
|
||||||
mSubscriptionInfoEntity == null ? null : mSubscriptionInfoEntity.carrierId,
|
|
||||||
cycleChartData.getStartTime(),
|
|
||||||
cycleChartData.getEndTime()
|
|
||||||
);
|
|
||||||
|
|
||||||
final long totalBytes = cycleChartData.getTotalUsage();
|
|
||||||
final CharSequence totalPhrase = DataUsageUtils.formatDataUsage(getActivity(), totalBytes);
|
|
||||||
mUsageAmount.setTitle(getString(R.string.data_used_template, totalPhrase));
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
final LoaderCallbacks<List<NetworkCycleChartData>> mNetworkCycleDataCallbacks =
|
|
||||||
new LoaderCallbacks<>() {
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public Loader<List<NetworkCycleChartData>> onCreateLoader(int id, Bundle args) {
|
|
||||||
return NetworkCycleChartDataLoader.builder(getContext())
|
|
||||||
.setNetworkTemplate(mTemplate)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(@NonNull Loader<List<NetworkCycleChartData>> loader,
|
|
||||||
List<NetworkCycleChartData> data) {
|
|
||||||
mLoadingViewController.showContent(false /* animate */);
|
|
||||||
mCycleData = data;
|
|
||||||
mDataUsageListHeaderController.updateCycleData(mCycleData);
|
|
||||||
updateConfigButtonVisibility();
|
|
||||||
mDataUsageListAppsController.setCycleData(mCycleData);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(@NonNull Loader<List<NetworkCycleChartData>> loader) {
|
|
||||||
mCycleData = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static boolean isGuestUser(Context context) {
|
|
||||||
if (context == null) return false;
|
|
||||||
final UserManager userManager = context.getSystemService(UserManager.class);
|
|
||||||
if (userManager == null) return false;
|
|
||||||
return userManager.isGuestUser();
|
|
||||||
}
|
|
||||||
}
|
|
229
src/com/android/settings/datausage/DataUsageList.kt
Normal file
229
src/com/android/settings/datausage/DataUsageList.kt
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.datausage
|
||||||
|
|
||||||
|
import android.app.settings.SettingsEnums
|
||||||
|
import android.net.NetworkPolicy
|
||||||
|
import android.net.NetworkTemplate
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.telephony.SubscriptionManager
|
||||||
|
import android.util.EventLog
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.OpenForTesting
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import com.android.settings.R
|
||||||
|
import com.android.settings.datausage.lib.BillingCycleRepository
|
||||||
|
import com.android.settings.datausage.lib.NetworkUsageData
|
||||||
|
import com.android.settings.network.MobileDataEnabledListener
|
||||||
|
import com.android.settings.network.MobileNetworkRepository
|
||||||
|
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity
|
||||||
|
import com.android.settingslib.spaprivileged.framework.common.userManager
|
||||||
|
import com.android.settingslib.utils.ThreadUtils
|
||||||
|
import kotlin.jvm.optionals.getOrNull
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Panel showing data usage history across various networks, including options
|
||||||
|
* to inspect based on usage cycle and control through [NetworkPolicy].
|
||||||
|
*/
|
||||||
|
@OpenForTesting
|
||||||
|
open class DataUsageList : DataUsageBaseFragment(), MobileDataEnabledListener.Client {
|
||||||
|
@VisibleForTesting
|
||||||
|
lateinit var dataStateListener: MobileDataEnabledListener
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
@VisibleForTesting
|
||||||
|
var template: NetworkTemplate? = null
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
@VisibleForTesting
|
||||||
|
var subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID
|
||||||
|
|
||||||
|
// Spinner will keep the selected cycle even after paused, this only keeps the displayed cycle,
|
||||||
|
// which need be cleared when resumed.
|
||||||
|
private var lastDisplayedUsageData: NetworkUsageData? = null
|
||||||
|
private lateinit var usageAmount: Preference
|
||||||
|
private var subscriptionInfoEntity: SubscriptionInfoEntity? = null
|
||||||
|
private lateinit var dataUsageListAppsController: DataUsageListAppsController
|
||||||
|
private lateinit var chartDataUsagePreferenceController: ChartDataUsagePreferenceController
|
||||||
|
private lateinit var billingCycleRepository: BillingCycleRepository
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
var dataUsageListHeaderController: DataUsageListHeaderController? = null
|
||||||
|
|
||||||
|
override fun getMetricsCategory() = SettingsEnums.DATA_USAGE_LIST
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
if (requireContext().userManager.isGuestUser) {
|
||||||
|
Log.e(TAG, "This setting isn't available for guest user")
|
||||||
|
EventLog.writeEvent(0x534e4554, "262741858", -1 /* UID */, "Guest user")
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
billingCycleRepository = createBillingCycleRepository();
|
||||||
|
if (!billingCycleRepository.isBandwidthControlEnabled()) {
|
||||||
|
Log.w(TAG, "No bandwidth control; leaving")
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
usageAmount = findPreference(KEY_USAGE_AMOUNT)!!
|
||||||
|
processArgument()
|
||||||
|
val template = template
|
||||||
|
if (template == null) {
|
||||||
|
Log.e(TAG, "No template; leaving")
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateSubscriptionInfoEntity()
|
||||||
|
dataStateListener = MobileDataEnabledListener(activity, this)
|
||||||
|
dataUsageListAppsController = use(DataUsageListAppsController::class.java).apply {
|
||||||
|
init(template)
|
||||||
|
}
|
||||||
|
chartDataUsagePreferenceController = use(ChartDataUsagePreferenceController::class.java)
|
||||||
|
chartDataUsagePreferenceController.init(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
open fun createBillingCycleRepository() = BillingCycleRepository(requireContext())
|
||||||
|
|
||||||
|
override fun onViewCreated(v: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(v, savedInstanceState)
|
||||||
|
|
||||||
|
val template = template ?: return
|
||||||
|
dataUsageListHeaderController = DataUsageListHeaderController(
|
||||||
|
setPinnedHeaderView(R.layout.apps_filter_spinner),
|
||||||
|
template,
|
||||||
|
metricsCategory,
|
||||||
|
viewLifecycleOwner,
|
||||||
|
::onCyclesLoad,
|
||||||
|
::updateSelectedCycle,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
dataStateListener.start(subId)
|
||||||
|
lastDisplayedUsageData = null
|
||||||
|
updatePolicy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
dataStateListener.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPreferenceScreenResId() = R.xml.data_usage_list
|
||||||
|
|
||||||
|
override fun getLogTag() = TAG
|
||||||
|
|
||||||
|
fun processArgument() {
|
||||||
|
arguments?.let {
|
||||||
|
subId = it.getInt(EXTRA_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID)
|
||||||
|
template = it.getParcelable(EXTRA_NETWORK_TEMPLATE, NetworkTemplate::class.java)
|
||||||
|
}
|
||||||
|
if (template == null && subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
|
||||||
|
subId = intent.getIntExtra(
|
||||||
|
Settings.EXTRA_SUB_ID,
|
||||||
|
SubscriptionManager.INVALID_SUBSCRIPTION_ID,
|
||||||
|
)
|
||||||
|
template = intent.getParcelableExtra(
|
||||||
|
Settings.EXTRA_NETWORK_TEMPLATE,
|
||||||
|
NetworkTemplate::class.java,
|
||||||
|
) ?: DataUsageUtils.getMobileNetworkTemplateFromSubId(context, intent).getOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
open fun updateSubscriptionInfoEntity() {
|
||||||
|
ThreadUtils.postOnBackgroundThread {
|
||||||
|
subscriptionInfoEntity =
|
||||||
|
MobileNetworkRepository.getInstance(context).getSubInfoById(subId.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of `MobileDataEnabledListener.Client`
|
||||||
|
*/
|
||||||
|
override fun onMobileDataEnabledChange() {
|
||||||
|
updatePolicy()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update chart sweeps and cycle list to reflect [NetworkPolicy] for current [template]. */
|
||||||
|
@VisibleForTesting
|
||||||
|
fun updatePolicy() {
|
||||||
|
val isBillingCycleModifiable = isBillingCycleModifiable()
|
||||||
|
dataUsageListHeaderController?.setConfigButtonVisible(isBillingCycleModifiable)
|
||||||
|
chartDataUsagePreferenceController.setBillingCycleModifiable(isBillingCycleModifiable)
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
open fun isBillingCycleModifiable(): Boolean {
|
||||||
|
return (billingCycleRepository.isModifiable(subId) &&
|
||||||
|
requireContext().getSystemService(SubscriptionManager::class.java)!!
|
||||||
|
.getActiveSubscriptionInfo(subId) != null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCyclesLoad(networkUsageData: List<NetworkUsageData>) {
|
||||||
|
dataUsageListAppsController.updateCycles(networkUsageData)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the chart and detail data when initial loaded or selected cycle changed.
|
||||||
|
*/
|
||||||
|
private fun updateSelectedCycle(usageData: NetworkUsageData) {
|
||||||
|
if (usageData == lastDisplayedUsageData) {
|
||||||
|
// Avoid duplicate update to avoid page flash.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastDisplayedUsageData = usageData
|
||||||
|
Log.d(TAG, "showing cycle $usageData")
|
||||||
|
|
||||||
|
val totalPhrase = DataUsageUtils.formatDataUsage(requireContext(), usageData.usage)
|
||||||
|
usageAmount.title = getString(R.string.data_used_template, totalPhrase)
|
||||||
|
|
||||||
|
updateChart(usageData)
|
||||||
|
updateApps(usageData)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates chart to show selected cycle. */
|
||||||
|
private fun updateChart(usageData: NetworkUsageData) {
|
||||||
|
chartDataUsagePreferenceController.update(
|
||||||
|
startTime = usageData.startTime,
|
||||||
|
endTime = usageData.endTime,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates applications data usage. */
|
||||||
|
private fun updateApps(usageData: NetworkUsageData) {
|
||||||
|
dataUsageListAppsController.update(
|
||||||
|
carrierId = subscriptionInfoEntity?.carrierId,
|
||||||
|
startTime = usageData.startTime,
|
||||||
|
endTime = usageData.endTime,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EXTRA_SUB_ID = "sub_id"
|
||||||
|
const val EXTRA_NETWORK_TEMPLATE = "network_template"
|
||||||
|
|
||||||
|
private const val TAG = "DataUsageList"
|
||||||
|
private const val KEY_USAGE_AMOUNT = "usage_amount"
|
||||||
|
}
|
||||||
|
}
|
@@ -31,8 +31,8 @@ import com.android.settings.R
|
|||||||
import com.android.settings.core.BasePreferenceController
|
import com.android.settings.core.BasePreferenceController
|
||||||
import com.android.settings.core.SubSettingLauncher
|
import com.android.settings.core.SubSettingLauncher
|
||||||
import com.android.settings.datausage.lib.AppDataUsageRepository
|
import com.android.settings.datausage.lib.AppDataUsageRepository
|
||||||
|
import com.android.settings.datausage.lib.NetworkUsageData
|
||||||
import com.android.settingslib.AppItem
|
import com.android.settingslib.AppItem
|
||||||
import com.android.settingslib.net.NetworkCycleChartData
|
|
||||||
import com.android.settingslib.net.UidDetailProvider
|
import com.android.settingslib.net.UidDetailProvider
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -48,7 +48,7 @@ open class DataUsageListAppsController(context: Context, preferenceKey: String)
|
|||||||
private lateinit var preference: PreferenceGroup
|
private lateinit var preference: PreferenceGroup
|
||||||
private lateinit var lifecycleScope: LifecycleCoroutineScope
|
private lateinit var lifecycleScope: LifecycleCoroutineScope
|
||||||
|
|
||||||
private var cycleData: List<NetworkCycleChartData>? = null
|
private var cycleData: List<NetworkUsageData>? = null
|
||||||
|
|
||||||
open fun init(template: NetworkTemplate) {
|
open fun init(template: NetworkTemplate) {
|
||||||
this.template = template
|
this.template = template
|
||||||
@@ -70,7 +70,7 @@ open class DataUsageListAppsController(context: Context, preferenceKey: String)
|
|||||||
lifecycleScope = viewLifecycleOwner.lifecycleScope
|
lifecycleScope = viewLifecycleOwner.lifecycleScope
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCycleData(cycleData: List<NetworkCycleChartData>?) {
|
fun updateCycles(cycleData: List<NetworkUsageData>) {
|
||||||
this.cycleData = cycleData
|
this.cycleData = cycleData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,25 +18,39 @@ package com.android.settings.datausage
|
|||||||
|
|
||||||
import android.net.NetworkTemplate
|
import android.net.NetworkTemplate
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Range
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.accessibility.AccessibilityEvent
|
import android.view.accessibility.AccessibilityEvent
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.Spinner
|
import android.widget.Spinner
|
||||||
import androidx.annotation.OpenForTesting
|
import androidx.annotation.OpenForTesting
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.android.settings.R
|
import com.android.settings.R
|
||||||
import com.android.settings.core.SubSettingLauncher
|
import com.android.settings.core.SubSettingLauncher
|
||||||
import com.android.settings.datausage.CycleAdapter.CycleItem
|
|
||||||
import com.android.settings.datausage.CycleAdapter.SpinnerInterface
|
import com.android.settings.datausage.CycleAdapter.SpinnerInterface
|
||||||
import com.android.settingslib.net.NetworkCycleChartData
|
import com.android.settings.datausage.lib.INetworkCycleDataRepository
|
||||||
|
import com.android.settings.datausage.lib.NetworkCycleDataRepository
|
||||||
|
import com.android.settings.datausage.lib.NetworkUsageData
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@OpenForTesting
|
@OpenForTesting
|
||||||
open class DataUsageListHeaderController(
|
open class DataUsageListHeaderController(
|
||||||
header: View,
|
header: View,
|
||||||
template: NetworkTemplate,
|
template: NetworkTemplate,
|
||||||
sourceMetricsCategory: Int,
|
sourceMetricsCategory: Int,
|
||||||
private val onItemSelected: (cycleItem: CycleItem, position: Int) -> Unit,
|
viewLifecycleOwner: LifecycleOwner,
|
||||||
|
private val onCyclesLoad: (usageDataList: List<NetworkUsageData>) -> Unit,
|
||||||
|
private val onItemSelected: (usageData: NetworkUsageData) -> Unit,
|
||||||
|
private val repository: INetworkCycleDataRepository =
|
||||||
|
NetworkCycleDataRepository(header.context, template),
|
||||||
) {
|
) {
|
||||||
private val context = header.context
|
private val context = header.context
|
||||||
|
|
||||||
private val configureButton: View = header.requireViewById(R.id.filter_settings)
|
private val configureButton: View = header.requireViewById(R.id.filter_settings)
|
||||||
private val cycleSpinner: Spinner = header.requireViewById(R.id.filter_spinner)
|
private val cycleSpinner: Spinner = header.requireViewById(R.id.filter_spinner)
|
||||||
private val cycleAdapter = CycleAdapter(context, object : SpinnerInterface {
|
private val cycleAdapter = CycleAdapter(context, object : SpinnerInterface {
|
||||||
@@ -50,13 +64,12 @@ open class DataUsageListHeaderController(
|
|||||||
cycleSpinner.setSelection(position)
|
cycleSpinner.setSelection(position)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
private var cycles: List<NetworkUsageData> = emptyList()
|
||||||
|
|
||||||
private val cycleListener = object : AdapterView.OnItemSelectedListener {
|
private val cycleListener = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
if (0 <= position && position < cycleAdapter.count) {
|
if (0 <= position && position < cycleAdapter.count) {
|
||||||
cycleAdapter.getItem(position)?.let { cycleItem ->
|
cycles.getOrNull(position)?.let(onItemSelected)
|
||||||
onItemSelected(cycleItem, position)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,24 +93,32 @@ open class DataUsageListHeaderController(
|
|||||||
cycleSpinner.visibility = View.GONE
|
cycleSpinner.visibility = View.GONE
|
||||||
cycleSpinner.accessibilityDelegate = object : View.AccessibilityDelegate() {
|
cycleSpinner.accessibilityDelegate = object : View.AccessibilityDelegate() {
|
||||||
override fun sendAccessibilityEvent(host: View, eventType: Int) {
|
override fun sendAccessibilityEvent(host: View, eventType: Int) {
|
||||||
if (eventType == AccessibilityEvent.TYPE_VIEW_SELECTED) {
|
|
||||||
// Ignore TYPE_VIEW_SELECTED or TalkBack will speak for it at onResume.
|
// Ignore TYPE_VIEW_SELECTED or TalkBack will speak for it at onResume.
|
||||||
return
|
if (eventType == AccessibilityEvent.TYPE_VIEW_SELECTED) return
|
||||||
}
|
|
||||||
super.sendAccessibilityEvent(host, eventType)
|
super.sendAccessibilityEvent(host, eventType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
cycles = withContext(Dispatchers.Default) {
|
||||||
|
repository.loadCycles()
|
||||||
|
}
|
||||||
|
updateCycleData()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun setConfigButtonVisible(visible: Boolean) {
|
open fun setConfigButtonVisible(visible: Boolean) {
|
||||||
configureButton.visibility = if (visible) View.VISIBLE else View.GONE
|
configureButton.visibility = if (visible) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun updateCycleData(cycleData: List<NetworkCycleChartData>) {
|
private fun updateCycleData() {
|
||||||
cycleSpinner.onItemSelectedListener = cycleListener
|
cycleSpinner.onItemSelectedListener = cycleListener
|
||||||
// calculate policy cycles based on available data
|
// calculate policy cycles based on available data
|
||||||
// generate cycle list based on policy and available history
|
// generate cycle list based on policy and available history
|
||||||
cycleAdapter.updateCycleList(cycleData)
|
cycleAdapter.updateCycleList(cycles.map { Range(it.startTime, it.endTime) })
|
||||||
cycleSpinner.visibility = View.VISIBLE
|
cycleSpinner.visibility = View.VISIBLE
|
||||||
|
onCyclesLoad(cycles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.datausage.lib
|
||||||
|
|
||||||
|
import kotlin.time.Duration.Companion.days
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usage data in a billing cycle with daily data for plotting the usage chart.
|
||||||
|
*/
|
||||||
|
data class NetworkCycleChartData(
|
||||||
|
val total: NetworkUsageData,
|
||||||
|
val dailyUsage: List<NetworkUsageData>,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val BUCKET_DURATION = 1.days
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.datausage.lib
|
||||||
|
|
||||||
|
import android.app.usage.NetworkStats
|
||||||
|
import android.app.usage.NetworkStatsManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.NetworkPolicy
|
||||||
|
import android.net.NetworkPolicyManager
|
||||||
|
import android.net.NetworkTemplate
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.Range
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import com.android.settingslib.NetworkPolicyEditor
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
|
||||||
|
interface INetworkCycleDataRepository {
|
||||||
|
suspend fun loadCycles(): List<NetworkUsageData>
|
||||||
|
fun getPolicy(): NetworkPolicy?
|
||||||
|
suspend fun querySummary(startTime: Long, endTime: Long): NetworkCycleChartData?
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkCycleDataRepository(
|
||||||
|
context: Context,
|
||||||
|
private val networkTemplate: NetworkTemplate,
|
||||||
|
) : INetworkCycleDataRepository {
|
||||||
|
private val networkStatsManager = context.getSystemService(NetworkStatsManager::class.java)!!
|
||||||
|
|
||||||
|
private val policyManager = context.getSystemService(NetworkPolicyManager::class.java)!!
|
||||||
|
|
||||||
|
override suspend fun loadCycles(): List<NetworkUsageData> =
|
||||||
|
getCycles().queryUsage().filter { it.usage > 0 }
|
||||||
|
|
||||||
|
private fun getCycles(): List<Range<Long>> {
|
||||||
|
val policy = getPolicy() ?: return queryCyclesAsFourWeeks()
|
||||||
|
return policy.cycleIterator().asSequence().map {
|
||||||
|
Range(it.lower.toInstant().toEpochMilli(), it.upper.toInstant().toEpochMilli())
|
||||||
|
}.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun queryCyclesAsFourWeeks(): List<Range<Long>> {
|
||||||
|
val timeRange = getTimeRange()
|
||||||
|
return reverseBucketRange(
|
||||||
|
startTime = timeRange.lower,
|
||||||
|
endTime = timeRange.upper,
|
||||||
|
bucketSize = DateUtils.WEEK_IN_MILLIS * 4,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
fun getTimeRange(): Range<Long> = getTimeRangeOf(
|
||||||
|
networkStatsManager.queryDetailsForDevice(networkTemplate, Long.MIN_VALUE, Long.MAX_VALUE)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getTimeRangeOf(stats: NetworkStats): Range<Long> {
|
||||||
|
var start = Long.MAX_VALUE
|
||||||
|
var end = Long.MIN_VALUE
|
||||||
|
val bucket = NetworkStats.Bucket()
|
||||||
|
while (stats.getNextBucket(bucket)) {
|
||||||
|
start = start.coerceAtMost(bucket.startTimeStamp)
|
||||||
|
end = end.coerceAtLeast(bucket.endTimeStamp)
|
||||||
|
}
|
||||||
|
return Range(start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPolicy(): NetworkPolicy? =
|
||||||
|
with(NetworkPolicyEditor(policyManager)) {
|
||||||
|
read()
|
||||||
|
getPolicy(networkTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun querySummary(startTime: Long, endTime: Long): NetworkCycleChartData? {
|
||||||
|
val usage = getUsage(startTime, endTime)
|
||||||
|
if (usage > 0L) {
|
||||||
|
return NetworkCycleChartData(
|
||||||
|
total = NetworkUsageData(startTime, endTime, usage),
|
||||||
|
dailyUsage = bucketRange(
|
||||||
|
startTime = startTime,
|
||||||
|
endTime = endTime,
|
||||||
|
bucketSize = NetworkCycleChartData.BUCKET_DURATION.inWholeMilliseconds,
|
||||||
|
).queryUsage(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun List<Range<Long>>.queryUsage(): List<NetworkUsageData> = coroutineScope {
|
||||||
|
map { range ->
|
||||||
|
async {
|
||||||
|
NetworkUsageData(
|
||||||
|
startTime = range.lower,
|
||||||
|
endTime = range.upper,
|
||||||
|
usage = getUsage(range.lower, range.upper),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.awaitAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bucketRange(startTime: Long, endTime: Long, bucketSize: Long): List<Range<Long>> {
|
||||||
|
val buckets = mutableListOf<Range<Long>>()
|
||||||
|
var currentStart = startTime
|
||||||
|
while (currentStart < endTime) {
|
||||||
|
val bucketEnd = currentStart + bucketSize
|
||||||
|
buckets += Range(currentStart, bucketEnd)
|
||||||
|
currentStart = bucketEnd
|
||||||
|
}
|
||||||
|
return buckets
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reverseBucketRange(
|
||||||
|
startTime: Long,
|
||||||
|
endTime: Long,
|
||||||
|
bucketSize: Long,
|
||||||
|
): List<Range<Long>> {
|
||||||
|
val buckets = mutableListOf<Range<Long>>()
|
||||||
|
var currentEnd = endTime
|
||||||
|
while (currentEnd > startTime) {
|
||||||
|
val bucketStart = currentEnd - bucketSize
|
||||||
|
buckets += Range(bucketStart, currentEnd)
|
||||||
|
currentEnd = bucketStart
|
||||||
|
}
|
||||||
|
return buckets
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getUsage(start: Long, end: Long): Long = try {
|
||||||
|
networkStatsManager.querySummaryForDevice(networkTemplate, start, end).let {
|
||||||
|
it.rxBytes + it.txBytes
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Exception querying network detail.", e)
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "NetworkCycleDataRepository"
|
||||||
|
}
|
||||||
|
}
|
26
src/com/android/settings/datausage/lib/NetworkUsageData.kt
Normal file
26
src/com/android/settings/datausage/lib/NetworkUsageData.kt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.datausage.lib
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base data structure representing usage data in a period.
|
||||||
|
*/
|
||||||
|
data class NetworkUsageData(
|
||||||
|
val startTime: Long,
|
||||||
|
val endTime: Long,
|
||||||
|
val usage: Long,
|
||||||
|
)
|
@@ -31,9 +31,9 @@ import androidx.preference.PreferenceViewHolder;
|
|||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.datausage.ChartDataUsagePreference.DataUsageSummaryNode;
|
import com.android.settings.datausage.ChartDataUsagePreference.DataUsageSummaryNode;
|
||||||
|
import com.android.settings.datausage.lib.NetworkCycleChartData;
|
||||||
|
import com.android.settings.datausage.lib.NetworkUsageData;
|
||||||
import com.android.settings.widget.UsageView;
|
import com.android.settings.widget.UsageView;
|
||||||
import com.android.settingslib.net.NetworkCycleChartData;
|
|
||||||
import com.android.settingslib.net.NetworkCycleData;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -55,7 +55,7 @@ public class ChartDataUsagePreferenceTest {
|
|||||||
// Test bucket end date, 22 Mar 2018 00:00:00
|
// Test bucket end date, 22 Mar 2018 00:00:00
|
||||||
private static final long TIMESTAMP_END = 1521676800000L;
|
private static final long TIMESTAMP_END = 1521676800000L;
|
||||||
|
|
||||||
private List<NetworkCycleData> mNetworkCycleData;
|
private List<NetworkUsageData> mNetworkCycleData;
|
||||||
private NetworkCycleChartData mNetworkCycleChartData;
|
private NetworkCycleChartData mNetworkCycleChartData;
|
||||||
private ChartDataUsagePreference mPreference;
|
private ChartDataUsagePreference mPreference;
|
||||||
private Activity mActivity;
|
private Activity mActivity;
|
||||||
@@ -79,6 +79,9 @@ public class ChartDataUsagePreferenceTest {
|
|||||||
final ArgumentCaptor<SparseIntArray> pointsCaptor =
|
final ArgumentCaptor<SparseIntArray> pointsCaptor =
|
||||||
ArgumentCaptor.forClass(SparseIntArray.class);
|
ArgumentCaptor.forClass(SparseIntArray.class);
|
||||||
createTestNetworkData();
|
createTestNetworkData();
|
||||||
|
mPreference.setTime(
|
||||||
|
mNetworkCycleChartData.getTotal().getStartTime(),
|
||||||
|
mNetworkCycleChartData.getTotal().getEndTime());
|
||||||
mPreference.setNetworkCycleData(mNetworkCycleChartData);
|
mPreference.setNetworkCycleData(mNetworkCycleChartData);
|
||||||
|
|
||||||
mPreference.calcPoints(usageView, mNetworkCycleData.subList(0, 5));
|
mPreference.calcPoints(usageView, mNetworkCycleData.subList(0, 5));
|
||||||
@@ -95,6 +98,9 @@ public class ChartDataUsagePreferenceTest {
|
|||||||
final ArgumentCaptor<SparseIntArray> pointsCaptor =
|
final ArgumentCaptor<SparseIntArray> pointsCaptor =
|
||||||
ArgumentCaptor.forClass(SparseIntArray.class);
|
ArgumentCaptor.forClass(SparseIntArray.class);
|
||||||
createTestNetworkData();
|
createTestNetworkData();
|
||||||
|
mPreference.setTime(
|
||||||
|
mNetworkCycleChartData.getTotal().getStartTime(),
|
||||||
|
mNetworkCycleChartData.getTotal().getEndTime());
|
||||||
mPreference.setNetworkCycleData(mNetworkCycleChartData);
|
mPreference.setNetworkCycleData(mNetworkCycleChartData);
|
||||||
|
|
||||||
mPreference.calcPoints(usageView, mNetworkCycleData.subList(2, 7));
|
mPreference.calcPoints(usageView, mNetworkCycleData.subList(2, 7));
|
||||||
@@ -114,35 +120,58 @@ public class ChartDataUsagePreferenceTest {
|
|||||||
final long tonight = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(12);
|
final long tonight = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(12);
|
||||||
mNetworkCycleData = new ArrayList<>();
|
mNetworkCycleData = new ArrayList<>();
|
||||||
// add test usage data for last 5 days
|
// add test usage data for last 5 days
|
||||||
mNetworkCycleData.add(createNetworkCycleData(
|
mNetworkCycleData.add(new NetworkUsageData(
|
||||||
tonight - TimeUnit.DAYS.toMillis(5), tonight - TimeUnit.DAYS.toMillis(4), 743823454L));
|
tonight - TimeUnit.DAYS.toMillis(5),
|
||||||
mNetworkCycleData.add(createNetworkCycleData(
|
tonight - TimeUnit.DAYS.toMillis(4),
|
||||||
tonight - TimeUnit.DAYS.toMillis(4), tonight - TimeUnit.DAYS.toMillis(3), 64396L));
|
743823454L));
|
||||||
mNetworkCycleData.add(createNetworkCycleData(
|
mNetworkCycleData.add(new NetworkUsageData(
|
||||||
tonight - TimeUnit.DAYS.toMillis(3), tonight - TimeUnit.DAYS.toMillis(2), 2832L));
|
tonight - TimeUnit.DAYS.toMillis(4),
|
||||||
mNetworkCycleData.add(createNetworkCycleData(
|
tonight - TimeUnit.DAYS.toMillis(3),
|
||||||
tonight - TimeUnit.DAYS.toMillis(2), tonight - TimeUnit.DAYS.toMillis(1), 83849690L));
|
64396L));
|
||||||
mNetworkCycleData.add(createNetworkCycleData(
|
mNetworkCycleData.add(new NetworkUsageData(
|
||||||
|
tonight - TimeUnit.DAYS.toMillis(3),
|
||||||
|
tonight - TimeUnit.DAYS.toMillis(2),
|
||||||
|
2832L));
|
||||||
|
mNetworkCycleData.add(new NetworkUsageData(
|
||||||
|
tonight - TimeUnit.DAYS.toMillis(2),
|
||||||
|
tonight - TimeUnit.DAYS.toMillis(1),
|
||||||
|
83849690L));
|
||||||
|
mNetworkCycleData.add(new NetworkUsageData(
|
||||||
tonight - TimeUnit.DAYS.toMillis(1), tonight, 1883657L));
|
tonight - TimeUnit.DAYS.toMillis(1), tonight, 1883657L));
|
||||||
// add test usage data for next 5 days
|
// add test usage data for next 5 days
|
||||||
mNetworkCycleData.add(createNetworkCycleData(
|
mNetworkCycleData.add(new NetworkUsageData(
|
||||||
tonight, tonight + TimeUnit.DAYS.toMillis(1), 0L));
|
tonight, tonight + TimeUnit.DAYS.toMillis(1), 0L));
|
||||||
mNetworkCycleData.add(createNetworkCycleData(
|
mNetworkCycleData.add(new NetworkUsageData(
|
||||||
tonight + TimeUnit.DAYS.toMillis(1), tonight + TimeUnit.DAYS.toMillis(2), 0L));
|
tonight + TimeUnit.DAYS.toMillis(1),
|
||||||
mNetworkCycleData.add(createNetworkCycleData(
|
tonight + TimeUnit.DAYS.toMillis(2),
|
||||||
tonight + TimeUnit.DAYS.toMillis(2), tonight + TimeUnit.DAYS.toMillis(3), 0L));
|
0L));
|
||||||
mNetworkCycleData.add(createNetworkCycleData(
|
mNetworkCycleData.add(new NetworkUsageData(
|
||||||
tonight + TimeUnit.DAYS.toMillis(3), tonight + TimeUnit.DAYS.toMillis(4), 0L));
|
tonight + TimeUnit.DAYS.toMillis(2),
|
||||||
mNetworkCycleData.add(createNetworkCycleData(
|
tonight + TimeUnit.DAYS.toMillis(3),
|
||||||
tonight + TimeUnit.DAYS.toMillis(4), tonight + TimeUnit.DAYS.toMillis(5), 0L));
|
0L));
|
||||||
mNetworkCycleData.add(createNetworkCycleData(
|
mNetworkCycleData.add(new NetworkUsageData(
|
||||||
tonight + TimeUnit.DAYS.toMillis(5), tonight + TimeUnit.DAYS.toMillis(6), 0L));
|
tonight + TimeUnit.DAYS.toMillis(3),
|
||||||
|
tonight + TimeUnit.DAYS.toMillis(4),
|
||||||
|
0L));
|
||||||
|
mNetworkCycleData.add(new NetworkUsageData(
|
||||||
|
tonight + TimeUnit.DAYS.toMillis(4),
|
||||||
|
tonight + TimeUnit.DAYS.toMillis(5),
|
||||||
|
0L));
|
||||||
|
mNetworkCycleData.add(new NetworkUsageData(
|
||||||
|
tonight + TimeUnit.DAYS.toMillis(5),
|
||||||
|
tonight + TimeUnit.DAYS.toMillis(6),
|
||||||
|
0L));
|
||||||
|
|
||||||
final NetworkCycleChartData.Builder builder = new NetworkCycleChartData.Builder();
|
mNetworkCycleChartData = new NetworkCycleChartData(
|
||||||
builder.setUsageBuckets(mNetworkCycleData)
|
new NetworkUsageData(
|
||||||
.setStartTime(tonight - TimeUnit.DAYS.toMillis(5))
|
tonight - TimeUnit.DAYS.toMillis(5),
|
||||||
.setEndTime(tonight + TimeUnit.DAYS.toMillis(6));
|
tonight + TimeUnit.DAYS.toMillis(6),
|
||||||
mNetworkCycleChartData = builder.build();
|
0),
|
||||||
|
mNetworkCycleData
|
||||||
|
);
|
||||||
|
mPreference.setTime(
|
||||||
|
mNetworkCycleChartData.getTotal().getStartTime(),
|
||||||
|
mNetworkCycleChartData.getTotal().getEndTime());
|
||||||
mPreference.setNetworkCycleData(mNetworkCycleChartData);
|
mPreference.setNetworkCycleData(mNetworkCycleChartData);
|
||||||
|
|
||||||
mPreference.calcPoints(usageView, mNetworkCycleData);
|
mPreference.calcPoints(usageView, mNetworkCycleData);
|
||||||
@@ -170,6 +199,9 @@ public class ChartDataUsagePreferenceTest {
|
|||||||
final TextView labelStart = (TextView) mHolder.findViewById(R.id.label_start);
|
final TextView labelStart = (TextView) mHolder.findViewById(R.id.label_start);
|
||||||
final TextView labelEnd = (TextView) mHolder.findViewById(R.id.label_end);
|
final TextView labelEnd = (TextView) mHolder.findViewById(R.id.label_end);
|
||||||
createTestNetworkData();
|
createTestNetworkData();
|
||||||
|
mPreference.setTime(
|
||||||
|
mNetworkCycleChartData.getTotal().getStartTime(),
|
||||||
|
mNetworkCycleChartData.getTotal().getEndTime());
|
||||||
mPreference.setNetworkCycleData(mNetworkCycleChartData);
|
mPreference.setNetworkCycleData(mNetworkCycleChartData);
|
||||||
|
|
||||||
mPreference.onBindViewHolder(mHolder);
|
mPreference.onBindViewHolder(mHolder);
|
||||||
@@ -198,38 +230,33 @@ public class ChartDataUsagePreferenceTest {
|
|||||||
private void createTestNetworkData() {
|
private void createTestNetworkData() {
|
||||||
mNetworkCycleData = new ArrayList<>();
|
mNetworkCycleData = new ArrayList<>();
|
||||||
// create 10 arbitrary network data
|
// create 10 arbitrary network data
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521583200000L, 1521586800000L, 743823454L));
|
mNetworkCycleData.add(new NetworkUsageData(1521583200000L, 1521586800000L, 743823454L));
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521586800000L, 1521590400000L, 64396L));
|
mNetworkCycleData.add(new NetworkUsageData(1521586800000L, 1521590400000L, 64396L));
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521590400000L, 1521655200000L, 2832L));
|
mNetworkCycleData.add(new NetworkUsageData(1521590400000L, 1521655200000L, 2832L));
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521655200000L, 1521658800000L, 83849690L));
|
mNetworkCycleData.add(new NetworkUsageData(1521655200000L, 1521658800000L, 83849690L));
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521658800000L, 1521662400000L, 1883657L));
|
mNetworkCycleData.add(new NetworkUsageData(1521658800000L, 1521662400000L, 1883657L));
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521662400000L, 1521666000000L, 705259L));
|
mNetworkCycleData.add(new NetworkUsageData(1521662400000L, 1521666000000L, 705259L));
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521666000000L, 1521669600000L, 216169L));
|
mNetworkCycleData.add(new NetworkUsageData(1521666000000L, 1521669600000L, 216169L));
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521669600000L, 1521673200000L, 6069175L));
|
mNetworkCycleData.add(new NetworkUsageData(1521669600000L, 1521673200000L, 6069175L));
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521673200000L, 1521676800000L, 120389L));
|
mNetworkCycleData.add(new NetworkUsageData(1521673200000L, 1521676800000L, 120389L));
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521676800000L, 1521678800000L, 29947L));
|
mNetworkCycleData.add(new NetworkUsageData(1521676800000L, 1521678800000L, 29947L));
|
||||||
|
|
||||||
final NetworkCycleChartData.Builder builder = new NetworkCycleChartData.Builder();
|
mNetworkCycleChartData = new NetworkCycleChartData(
|
||||||
builder.setUsageBuckets(mNetworkCycleData)
|
new NetworkUsageData(TIMESTAMP_START, TIMESTAMP_END, 0),
|
||||||
.setStartTime(TIMESTAMP_START)
|
mNetworkCycleData
|
||||||
.setEndTime(TIMESTAMP_END);
|
);
|
||||||
mNetworkCycleChartData = builder.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createSomeSamePercentageNetworkData() {
|
private void createSomeSamePercentageNetworkData() {
|
||||||
mNetworkCycleData = new ArrayList<>();
|
mNetworkCycleData = new ArrayList<>();
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521583200000L, 1521586800000L, 100));//33%
|
mNetworkCycleData.add(new NetworkUsageData(1521583200000L, 1521586800000L, 100)); //33%
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521586800000L, 1521590400000L, 1)); //33%
|
mNetworkCycleData.add(new NetworkUsageData(1521586800000L, 1521590400000L, 1)); //33%
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521590400000L, 1521655200000L, 0)); //33%
|
mNetworkCycleData.add(new NetworkUsageData(1521590400000L, 1521655200000L, 0)); //33%
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521655200000L, 1521658800000L, 0)); //33%
|
mNetworkCycleData.add(new NetworkUsageData(1521655200000L, 1521658800000L, 0)); //33%
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521658800000L, 1521662400000L, 200));//99%
|
mNetworkCycleData.add(new NetworkUsageData(1521658800000L, 1521662400000L, 200)); //99%
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521662400000L, 1521666000000L, 1)); //99%
|
mNetworkCycleData.add(new NetworkUsageData(1521662400000L, 1521666000000L, 1)); //99%
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521666000000L, 1521669600000L, 1)); //100
|
mNetworkCycleData.add(new NetworkUsageData(1521666000000L, 1521669600000L, 1)); //100
|
||||||
mNetworkCycleData.add(createNetworkCycleData(1521669600000L, 1521673200000L, 0)); //100%
|
mNetworkCycleData.add(new NetworkUsageData(1521669600000L, 1521673200000L, 0)); //100%
|
||||||
}
|
}
|
||||||
|
|
||||||
private NetworkCycleData createNetworkCycleData(long start, long end, long usage) {
|
|
||||||
return new NetworkCycleData.Builder()
|
|
||||||
.setStartTime(start).setEndTime(end).setTotalUsage(usage).build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,249 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.datausage;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.Mockito.doNothing;
|
|
||||||
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;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.NetworkTemplate;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.UserManager;
|
|
||||||
import android.provider.Settings;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.loader.app.LoaderManager;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
import androidx.preference.PreferenceManager;
|
|
||||||
|
|
||||||
import com.android.settings.datausage.lib.BillingCycleRepository;
|
|
||||||
import com.android.settings.network.MobileDataEnabledListener;
|
|
||||||
import com.android.settings.testutils.FakeFeatureFactory;
|
|
||||||
import com.android.settings.widget.LoadingViewController;
|
|
||||||
import com.android.settingslib.NetworkPolicyEditor;
|
|
||||||
import com.android.settingslib.core.AbstractPreferenceController;
|
|
||||||
import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin;
|
|
||||||
import com.android.settingslib.net.NetworkCycleChartData;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.Spy;
|
|
||||||
import org.mockito.junit.MockitoJUnit;
|
|
||||||
import org.mockito.junit.MockitoRule;
|
|
||||||
import org.robolectric.Robolectric;
|
|
||||||
import org.robolectric.RobolectricTestRunner;
|
|
||||||
import org.robolectric.android.controller.ActivityController;
|
|
||||||
import org.robolectric.annotation.Config;
|
|
||||||
import org.robolectric.annotation.Implementation;
|
|
||||||
import org.robolectric.annotation.Implements;
|
|
||||||
import org.robolectric.util.ReflectionHelpers;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
|
||||||
@Config(shadows = DataUsageListTest.ShadowDataUsageBaseFragment.class)
|
|
||||||
public class DataUsageListTest {
|
|
||||||
@Rule
|
|
||||||
public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private MobileDataEnabledListener mMobileDataEnabledListener;
|
|
||||||
@Mock
|
|
||||||
private TemplatePreference.NetworkServices mNetworkServices;
|
|
||||||
@Mock
|
|
||||||
private LoaderManager mLoaderManager;
|
|
||||||
@Mock
|
|
||||||
private UserManager mUserManager;
|
|
||||||
@Mock
|
|
||||||
private BillingCycleRepository mBillingCycleRepository;
|
|
||||||
@Mock
|
|
||||||
private DataUsageListHeaderController mDataUsageListHeaderController;
|
|
||||||
|
|
||||||
private Activity mActivity;
|
|
||||||
|
|
||||||
@Spy
|
|
||||||
private TestDataUsageList mDataUsageList;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
FakeFeatureFactory.setupForTest();
|
|
||||||
final ActivityController<Activity> mActivityController =
|
|
||||||
Robolectric.buildActivity(Activity.class);
|
|
||||||
mActivity = spy(mActivityController.get());
|
|
||||||
mNetworkServices.mPolicyEditor = mock(NetworkPolicyEditor.class);
|
|
||||||
mDataUsageList.mDataStateListener = mMobileDataEnabledListener;
|
|
||||||
|
|
||||||
doReturn(mActivity).when(mDataUsageList).getContext();
|
|
||||||
doReturn(mUserManager).when(mActivity).getSystemService(UserManager.class);
|
|
||||||
doReturn(false).when(mUserManager).isGuestUser();
|
|
||||||
ReflectionHelpers.setField(mDataUsageList, "mDataStateListener",
|
|
||||||
mMobileDataEnabledListener);
|
|
||||||
ReflectionHelpers.setField(mDataUsageList, "services", mNetworkServices);
|
|
||||||
doReturn(mLoaderManager).when(mDataUsageList).getLoaderManager();
|
|
||||||
mDataUsageList.mLoadingViewController = mock(LoadingViewController.class);
|
|
||||||
doNothing().when(mDataUsageList).updateSubscriptionInfoEntity();
|
|
||||||
when(mBillingCycleRepository.isBandwidthControlEnabled()).thenReturn(true);
|
|
||||||
mDataUsageList.mDataUsageListHeaderController = mDataUsageListHeaderController;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onCreate_isNotGuestUser_shouldNotFinish() {
|
|
||||||
mDataUsageList.mTemplate = mock(NetworkTemplate.class);
|
|
||||||
doReturn(false).when(mUserManager).isGuestUser();
|
|
||||||
doNothing().when(mDataUsageList).processArgument();
|
|
||||||
|
|
||||||
mDataUsageList.onCreate(null);
|
|
||||||
|
|
||||||
verify(mDataUsageList, never()).finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onCreate_isGuestUser_shouldFinish() {
|
|
||||||
doReturn(true).when(mUserManager).isGuestUser();
|
|
||||||
|
|
||||||
mDataUsageList.onCreate(null);
|
|
||||||
|
|
||||||
verify(mDataUsageList).finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void resume_shouldListenDataStateChange() {
|
|
||||||
mDataUsageList.onCreate(null);
|
|
||||||
ReflectionHelpers.setField(
|
|
||||||
mDataUsageList, "mVisibilityLoggerMixin", mock(VisibilityLoggerMixin.class));
|
|
||||||
ReflectionHelpers.setField(
|
|
||||||
mDataUsageList, "mPreferenceManager", mock(PreferenceManager.class));
|
|
||||||
|
|
||||||
mDataUsageList.onResume();
|
|
||||||
|
|
||||||
verify(mMobileDataEnabledListener).start(anyInt());
|
|
||||||
|
|
||||||
mDataUsageList.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void pause_shouldUnlistenDataStateChange() {
|
|
||||||
mDataUsageList.onCreate(null);
|
|
||||||
ReflectionHelpers.setField(
|
|
||||||
mDataUsageList, "mVisibilityLoggerMixin", mock(VisibilityLoggerMixin.class));
|
|
||||||
ReflectionHelpers.setField(
|
|
||||||
mDataUsageList, "mPreferenceManager", mock(PreferenceManager.class));
|
|
||||||
|
|
||||||
mDataUsageList.onResume();
|
|
||||||
mDataUsageList.onPause();
|
|
||||||
|
|
||||||
verify(mMobileDataEnabledListener).stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void processArgument_shouldGetTemplateFromArgument() {
|
|
||||||
final Bundle args = new Bundle();
|
|
||||||
args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mock(NetworkTemplate.class));
|
|
||||||
args.putInt(DataUsageList.EXTRA_SUB_ID, 3);
|
|
||||||
mDataUsageList.setArguments(args);
|
|
||||||
|
|
||||||
mDataUsageList.processArgument();
|
|
||||||
|
|
||||||
assertThat(mDataUsageList.mTemplate).isNotNull();
|
|
||||||
assertThat(mDataUsageList.mSubId).isEqualTo(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void processArgument_fromIntent_shouldGetTemplateFromIntent() {
|
|
||||||
final Intent intent = new Intent();
|
|
||||||
intent.putExtra(Settings.EXTRA_NETWORK_TEMPLATE, mock(NetworkTemplate.class));
|
|
||||||
intent.putExtra(Settings.EXTRA_SUB_ID, 3);
|
|
||||||
doReturn(intent).when(mDataUsageList).getIntent();
|
|
||||||
|
|
||||||
mDataUsageList.processArgument();
|
|
||||||
|
|
||||||
assertThat(mDataUsageList.mTemplate).isNotNull();
|
|
||||||
assertThat(mDataUsageList.mSubId).isEqualTo(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onLoadFinished_networkCycleDataCallback_shouldShowCycleSpinner() {
|
|
||||||
mDataUsageList.mTemplate = mock(NetworkTemplate.class);
|
|
||||||
mDataUsageList.onCreate(null);
|
|
||||||
mDataUsageList.updatePolicy();
|
|
||||||
List<NetworkCycleChartData> mockData = Collections.emptyList();
|
|
||||||
|
|
||||||
mDataUsageList.mNetworkCycleDataCallbacks.onLoadFinished(null, mockData);
|
|
||||||
|
|
||||||
verify(mDataUsageListHeaderController).updateCycleData(mockData);
|
|
||||||
verify(mDataUsageListHeaderController).setConfigButtonVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onPause_shouldDestroyLoaders() {
|
|
||||||
mDataUsageList.onPause();
|
|
||||||
|
|
||||||
verify(mLoaderManager).destroyLoader(DataUsageList.LOADER_CHART_DATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Implements(DataUsageBaseFragment.class)
|
|
||||||
public static class ShadowDataUsageBaseFragment {
|
|
||||||
@Implementation
|
|
||||||
public void onCreate(Bundle icicle) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TestDataUsageList extends DataUsageList {
|
|
||||||
@Override
|
|
||||||
protected <T extends AbstractPreferenceController> T use(Class<T> clazz) {
|
|
||||||
return mock(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends Preference> T findPreference(CharSequence key) {
|
|
||||||
if (key.toString().equals("chart_data")) {
|
|
||||||
return (T) mock(ChartDataUsagePreference.class);
|
|
||||||
}
|
|
||||||
return (T) mock(Preference.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Intent getIntent() {
|
|
||||||
return new Intent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
BillingCycleRepository createBillingCycleRepository() {
|
|
||||||
return mBillingCycleRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean isBillingCycleModifiable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.datausage
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.NetworkTemplate
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.UserManager
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import com.android.settings.datausage.DataUsageListTest.ShadowDataUsageBaseFragment
|
||||||
|
import com.android.settings.datausage.TemplatePreference.NetworkServices
|
||||||
|
import com.android.settings.datausage.lib.BillingCycleRepository
|
||||||
|
import com.android.settings.network.MobileDataEnabledListener
|
||||||
|
import com.android.settings.testutils.FakeFeatureFactory
|
||||||
|
import com.android.settingslib.NetworkPolicyEditor
|
||||||
|
import com.android.settingslib.core.AbstractPreferenceController
|
||||||
|
import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.ArgumentMatchers
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.doNothing
|
||||||
|
import org.mockito.Mockito.doReturn
|
||||||
|
import org.mockito.Mockito.mock
|
||||||
|
import org.mockito.Mockito.never
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.Mockito.`when`
|
||||||
|
import org.mockito.Spy
|
||||||
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
import org.mockito.junit.MockitoRule
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
import org.robolectric.annotation.Implementation
|
||||||
|
import org.robolectric.annotation.Implements
|
||||||
|
import org.robolectric.util.ReflectionHelpers
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(shadows = [ShadowDataUsageBaseFragment::class])
|
||||||
|
class DataUsageListTest {
|
||||||
|
@get:Rule
|
||||||
|
val mockito: MockitoRule = MockitoJUnit.rule()
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var mobileDataEnabledListener: MobileDataEnabledListener
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var networkServices: NetworkServices
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var userManager: UserManager
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var billingCycleRepository: BillingCycleRepository
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var dataUsageListHeaderController: DataUsageListHeaderController
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
private val dataUsageList = TestDataUsageList()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
FakeFeatureFactory.setupForTest()
|
||||||
|
networkServices.mPolicyEditor = mock(NetworkPolicyEditor::class.java)
|
||||||
|
dataUsageList.dataStateListener = mobileDataEnabledListener
|
||||||
|
doReturn(context).`when`(dataUsageList).context
|
||||||
|
doReturn(userManager).`when`(context).getSystemService(UserManager::class.java)
|
||||||
|
doReturn(false).`when`(userManager).isGuestUser
|
||||||
|
ReflectionHelpers.setField(dataUsageList, "services", networkServices)
|
||||||
|
doNothing().`when`(dataUsageList).updateSubscriptionInfoEntity()
|
||||||
|
`when`(billingCycleRepository.isBandwidthControlEnabled()).thenReturn(true)
|
||||||
|
dataUsageList.dataUsageListHeaderController = dataUsageListHeaderController
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onCreate_isNotGuestUser_shouldNotFinish() {
|
||||||
|
dataUsageList.template = mock<NetworkTemplate>(NetworkTemplate::class.java)
|
||||||
|
doReturn(false).`when`(userManager).isGuestUser
|
||||||
|
doNothing().`when`(dataUsageList).processArgument()
|
||||||
|
dataUsageList.onCreate(null)
|
||||||
|
verify(dataUsageList, never()).finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onCreate_isGuestUser_shouldFinish() {
|
||||||
|
doReturn(true).`when`(userManager).isGuestUser
|
||||||
|
dataUsageList.onCreate(null)
|
||||||
|
verify(dataUsageList).finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun resume_shouldListenDataStateChange() {
|
||||||
|
dataUsageList.template = mock(NetworkTemplate::class.java)
|
||||||
|
dataUsageList.onCreate(null)
|
||||||
|
dataUsageList.dataStateListener = mobileDataEnabledListener
|
||||||
|
ReflectionHelpers.setField(
|
||||||
|
dataUsageList,
|
||||||
|
"mVisibilityLoggerMixin",
|
||||||
|
mock(VisibilityLoggerMixin::class.java),
|
||||||
|
)
|
||||||
|
ReflectionHelpers.setField(
|
||||||
|
dataUsageList,
|
||||||
|
"mPreferenceManager",
|
||||||
|
mock(PreferenceManager::class.java),
|
||||||
|
)
|
||||||
|
dataUsageList.onResume()
|
||||||
|
verify(mobileDataEnabledListener).start(ArgumentMatchers.anyInt())
|
||||||
|
dataUsageList.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun pause_shouldUnlistenDataStateChange() {
|
||||||
|
dataUsageList.template = mock(NetworkTemplate::class.java)
|
||||||
|
dataUsageList.onCreate(null)
|
||||||
|
dataUsageList.dataStateListener = mobileDataEnabledListener
|
||||||
|
ReflectionHelpers.setField(
|
||||||
|
dataUsageList, "mVisibilityLoggerMixin", mock(
|
||||||
|
VisibilityLoggerMixin::class.java
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ReflectionHelpers.setField(
|
||||||
|
dataUsageList, "mPreferenceManager", mock(
|
||||||
|
PreferenceManager::class.java
|
||||||
|
)
|
||||||
|
)
|
||||||
|
dataUsageList.onResume()
|
||||||
|
dataUsageList.onPause()
|
||||||
|
verify(mobileDataEnabledListener).stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun processArgument_shouldGetTemplateFromArgument() {
|
||||||
|
val args = Bundle()
|
||||||
|
args.putParcelable(
|
||||||
|
DataUsageList.EXTRA_NETWORK_TEMPLATE, mock(
|
||||||
|
NetworkTemplate::class.java
|
||||||
|
)
|
||||||
|
)
|
||||||
|
args.putInt(DataUsageList.EXTRA_SUB_ID, 3)
|
||||||
|
dataUsageList.arguments = args
|
||||||
|
dataUsageList.processArgument()
|
||||||
|
assertThat(dataUsageList.template).isNotNull()
|
||||||
|
assertThat(dataUsageList.subId).isEqualTo(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun processArgument_fromIntent_shouldGetTemplateFromIntent() {
|
||||||
|
val intent = Intent()
|
||||||
|
intent.putExtra(
|
||||||
|
Settings.EXTRA_NETWORK_TEMPLATE, mock(
|
||||||
|
NetworkTemplate::class.java
|
||||||
|
)
|
||||||
|
)
|
||||||
|
intent.putExtra(Settings.EXTRA_SUB_ID, 3)
|
||||||
|
doReturn(intent).`when`(dataUsageList).intent
|
||||||
|
dataUsageList.processArgument()
|
||||||
|
assertThat(dataUsageList.template).isNotNull()
|
||||||
|
assertThat(dataUsageList.subId).isEqualTo(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updatePolicy_setConfigButtonVisible() {
|
||||||
|
dataUsageList.template = mock(NetworkTemplate::class.java)
|
||||||
|
dataUsageList.onCreate(null)
|
||||||
|
|
||||||
|
dataUsageList.updatePolicy()
|
||||||
|
|
||||||
|
verify(dataUsageListHeaderController).setConfigButtonVisible(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implements(DataUsageBaseFragment::class)
|
||||||
|
class ShadowDataUsageBaseFragment {
|
||||||
|
@Implementation
|
||||||
|
fun onCreate(@Suppress("UNUSED_PARAMETER") icicle: Bundle?) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open inner class TestDataUsageList : DataUsageList() {
|
||||||
|
override fun <T : AbstractPreferenceController?> use(clazz: Class<T>): T = mock(clazz)
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : Preference?> findPreference(key: CharSequence): T =
|
||||||
|
mock(Preference::class.java) as T
|
||||||
|
|
||||||
|
public override fun getIntent() = Intent()
|
||||||
|
|
||||||
|
override fun createBillingCycleRepository() = billingCycleRepository
|
||||||
|
|
||||||
|
override fun isBillingCycleModifiable() = true
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.datausage
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.testing.TestLifecycleOwner
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.android.settings.datausage.lib.INetworkCycleDataRepository
|
||||||
|
import com.android.settings.datausage.lib.NetworkCycleChartData
|
||||||
|
import com.android.settings.datausage.lib.NetworkUsageData
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ChartDataUsagePreferenceControllerTest {
|
||||||
|
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||||
|
|
||||||
|
private val repository = object : INetworkCycleDataRepository {
|
||||||
|
override suspend fun loadCycles() = emptyList<NetworkUsageData>()
|
||||||
|
|
||||||
|
override fun getPolicy() = null
|
||||||
|
|
||||||
|
override suspend fun querySummary(startTime: Long, endTime: Long) = when {
|
||||||
|
startTime == START_TIME && endTime == END_TIME -> CycleChartDate
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val preference = mock<ChartDataUsagePreference>()
|
||||||
|
private val preferenceScreen = mock<PreferenceScreen> {
|
||||||
|
onGeneric { findPreference(KEY) } doReturn preference
|
||||||
|
}
|
||||||
|
|
||||||
|
private val controller = ChartDataUsagePreferenceController(context, KEY)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
controller.init(repository)
|
||||||
|
controller.displayPreference(preferenceScreen)
|
||||||
|
controller.onViewCreated(TestLifecycleOwner())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun update() = runBlocking {
|
||||||
|
controller.update(START_TIME, END_TIME)
|
||||||
|
delay(100L)
|
||||||
|
|
||||||
|
verify(preference).setTime(START_TIME, END_TIME)
|
||||||
|
verify(preference).setNetworkCycleData(CycleChartDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val KEY = "test_key"
|
||||||
|
const val START_TIME = 1L
|
||||||
|
const val END_TIME = 2L
|
||||||
|
|
||||||
|
val UsageData = NetworkUsageData(startTime = START_TIME, endTime = END_TIME, usage = 10)
|
||||||
|
val CycleChartDate =
|
||||||
|
NetworkCycleChartData(total = UsageData, dailyUsage = listOf(UsageData))
|
||||||
|
}
|
||||||
|
}
|
@@ -22,8 +22,8 @@ import android.net.NetworkTemplate
|
|||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.android.settings.SettingsActivity
|
import com.android.settings.SettingsActivity
|
||||||
|
import com.android.settings.datausage.lib.NetworkUsageData
|
||||||
import com.android.settingslib.AppItem
|
import com.android.settingslib.AppItem
|
||||||
import com.android.settingslib.net.NetworkCycleChartData
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -48,11 +48,8 @@ class DataUsageListAppsControllerTest {
|
|||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
controller.init(mock<NetworkTemplate>())
|
controller.init(mock<NetworkTemplate>())
|
||||||
val data = NetworkCycleChartData.Builder().apply {
|
val data = NetworkUsageData(START_TIME, END_TIME, 0)
|
||||||
setStartTime(START_TIME)
|
controller.updateCycles(listOf(data))
|
||||||
setEndTime(END_TIME)
|
|
||||||
}.build()
|
|
||||||
controller.setCycleData(listOf(data))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@@ -21,10 +21,16 @@ import android.net.NetworkTemplate
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Spinner
|
import android.widget.Spinner
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.testing.TestLifecycleOwner
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.android.settings.R
|
import com.android.settings.R
|
||||||
|
import com.android.settings.datausage.lib.INetworkCycleDataRepository
|
||||||
|
import com.android.settings.datausage.lib.NetworkUsageData
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.kotlin.any
|
import org.mockito.kotlin.any
|
||||||
@@ -40,6 +46,14 @@ class DataUsageListHeaderControllerTest {
|
|||||||
doNothing().whenever(mock).startActivity(any())
|
doNothing().whenever(mock).startActivity(any())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val repository = object : INetworkCycleDataRepository {
|
||||||
|
override suspend fun loadCycles() = emptyList<NetworkUsageData>()
|
||||||
|
|
||||||
|
override fun getPolicy() = null
|
||||||
|
|
||||||
|
override suspend fun querySummary(startTime: Long, endTime: Long) = null
|
||||||
|
}
|
||||||
|
|
||||||
private val header =
|
private val header =
|
||||||
LayoutInflater.from(context).inflate(R.layout.apps_filter_spinner, null, false)
|
LayoutInflater.from(context).inflate(R.layout.apps_filter_spinner, null, false)
|
||||||
|
|
||||||
@@ -47,11 +61,16 @@ class DataUsageListHeaderControllerTest {
|
|||||||
|
|
||||||
private val spinner: Spinner = header.requireViewById(R.id.filter_spinner)
|
private val spinner: Spinner = header.requireViewById(R.id.filter_spinner)
|
||||||
|
|
||||||
|
private val testLifecycleOwner = TestLifecycleOwner(initialState = Lifecycle.State.CREATED)
|
||||||
|
|
||||||
private val controller = DataUsageListHeaderController(
|
private val controller = DataUsageListHeaderController(
|
||||||
header = header,
|
header = header,
|
||||||
template = mock<NetworkTemplate>(),
|
template = mock<NetworkTemplate>(),
|
||||||
sourceMetricsCategory = 0,
|
sourceMetricsCategory = 0,
|
||||||
onItemSelected = { _, _ -> },
|
viewLifecycleOwner = testLifecycleOwner,
|
||||||
|
onCyclesLoad = {},
|
||||||
|
onItemSelected = {},
|
||||||
|
repository = repository,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -60,8 +79,9 @@ class DataUsageListHeaderControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun updateCycleData_shouldShowCycleSpinner() {
|
fun updateCycleData_shouldShowCycleSpinner() = runBlocking {
|
||||||
controller.updateCycleData(emptyList())
|
testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
||||||
|
delay(100)
|
||||||
|
|
||||||
assertThat(spinner.visibility).isEqualTo(View.VISIBLE)
|
assertThat(spinner.visibility).isEqualTo(View.VISIBLE)
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.datausage.lib
|
||||||
|
|
||||||
|
import android.app.usage.NetworkStats.Bucket
|
||||||
|
import android.app.usage.NetworkStatsManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.NetworkPolicy
|
||||||
|
import android.net.NetworkTemplate
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import android.util.Range
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.kotlin.any
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.eq
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.spy
|
||||||
|
import org.mockito.kotlin.whenever
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class NetworkCycleDataRepositoryTest {
|
||||||
|
private val mockNetworkStatsManager = mock<NetworkStatsManager> {
|
||||||
|
on { querySummaryForDevice(any(), eq(CYCLE1_START_TIME), eq(CYCLE1_END_TIME)) } doReturn
|
||||||
|
CYCLE1_BUCKET
|
||||||
|
|
||||||
|
on {
|
||||||
|
querySummaryForDevice(
|
||||||
|
any(),
|
||||||
|
eq(CYCLE2_END_TIME - DateUtils.WEEK_IN_MILLIS * 4),
|
||||||
|
eq(CYCLE2_END_TIME),
|
||||||
|
)
|
||||||
|
} doReturn CYCLE2_BUCKET
|
||||||
|
|
||||||
|
on { querySummaryForDevice(any(), eq(CYCLE3_START_TIME), eq(CYCLE4_END_TIME)) } doReturn
|
||||||
|
CYCLE3_AND_4_BUCKET
|
||||||
|
|
||||||
|
on { querySummaryForDevice(any(), eq(CYCLE3_START_TIME), eq(CYCLE3_END_TIME)) } doReturn
|
||||||
|
CYCLE3_BUCKET
|
||||||
|
|
||||||
|
on { querySummaryForDevice(any(), eq(CYCLE4_START_TIME), eq(CYCLE4_END_TIME)) } doReturn
|
||||||
|
CYCLE4_BUCKET
|
||||||
|
}
|
||||||
|
|
||||||
|
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
|
||||||
|
on { getSystemService(NetworkStatsManager::class.java) } doReturn mockNetworkStatsManager
|
||||||
|
}
|
||||||
|
|
||||||
|
private val template = mock<NetworkTemplate>()
|
||||||
|
|
||||||
|
private val repository = spy(NetworkCycleDataRepository(context, template))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun loadCycles_byPolicy() = runTest {
|
||||||
|
val policy = mock<NetworkPolicy> {
|
||||||
|
on { cycleIterator() } doReturn listOf(
|
||||||
|
Range(zonedDateTime(CYCLE1_START_TIME), zonedDateTime(CYCLE1_END_TIME))
|
||||||
|
).iterator()
|
||||||
|
}
|
||||||
|
doReturn(policy).whenever(repository).getPolicy()
|
||||||
|
|
||||||
|
val cycles = repository.loadCycles()
|
||||||
|
|
||||||
|
assertThat(cycles).containsExactly(NetworkUsageData(startTime = 1, endTime = 2, usage = 11))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun loadCycles_asFourWeeks() = runTest {
|
||||||
|
doReturn(null).whenever(repository).getPolicy()
|
||||||
|
doReturn(Range(CYCLE2_START_TIME, CYCLE2_END_TIME)).whenever(repository).getTimeRange()
|
||||||
|
|
||||||
|
val cycles = repository.loadCycles()
|
||||||
|
|
||||||
|
assertThat(cycles).containsExactly(
|
||||||
|
NetworkUsageData(
|
||||||
|
startTime = CYCLE2_END_TIME - DateUtils.WEEK_IN_MILLIS * 4,
|
||||||
|
endTime = CYCLE2_END_TIME,
|
||||||
|
usage = 22,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun querySummary() = runTest {
|
||||||
|
val summary = repository.querySummary(CYCLE3_START_TIME, CYCLE4_END_TIME)
|
||||||
|
|
||||||
|
assertThat(summary).isEqualTo(
|
||||||
|
NetworkCycleChartData(
|
||||||
|
total = NetworkUsageData(
|
||||||
|
startTime = CYCLE3_START_TIME,
|
||||||
|
endTime = CYCLE4_END_TIME,
|
||||||
|
usage = 77,
|
||||||
|
),
|
||||||
|
dailyUsage = listOf(
|
||||||
|
NetworkUsageData(
|
||||||
|
startTime = CYCLE3_START_TIME,
|
||||||
|
endTime = CYCLE3_END_TIME,
|
||||||
|
usage = 33,
|
||||||
|
),
|
||||||
|
NetworkUsageData(
|
||||||
|
startTime = CYCLE4_START_TIME,
|
||||||
|
endTime = CYCLE4_END_TIME,
|
||||||
|
usage = 44,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun zonedDateTime(epochMilli: Long): ZonedDateTime? =
|
||||||
|
ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), ZoneId.systemDefault())
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val CYCLE1_START_TIME = 1L
|
||||||
|
const val CYCLE1_END_TIME = 2L
|
||||||
|
val CYCLE1_BUCKET = mock<Bucket> {
|
||||||
|
on { rxBytes } doReturn 1
|
||||||
|
on { txBytes } doReturn 10
|
||||||
|
}
|
||||||
|
|
||||||
|
const val CYCLE2_START_TIME = 1695555555000L
|
||||||
|
const val CYCLE2_END_TIME = 1695566666000L
|
||||||
|
val CYCLE2_BUCKET = mock<Bucket> {
|
||||||
|
on { rxBytes } doReturn 2
|
||||||
|
on { txBytes } doReturn 20
|
||||||
|
}
|
||||||
|
|
||||||
|
const val CYCLE3_START_TIME = 1695555555000L
|
||||||
|
const val CYCLE3_END_TIME = CYCLE3_START_TIME + DateUtils.DAY_IN_MILLIS
|
||||||
|
val CYCLE3_BUCKET = mock<Bucket> {
|
||||||
|
on { rxBytes } doReturn 3
|
||||||
|
on { txBytes } doReturn 30
|
||||||
|
}
|
||||||
|
|
||||||
|
const val CYCLE4_START_TIME = CYCLE3_END_TIME
|
||||||
|
const val CYCLE4_END_TIME = CYCLE4_START_TIME + DateUtils.DAY_IN_MILLIS
|
||||||
|
val CYCLE4_BUCKET = mock<Bucket> {
|
||||||
|
on { rxBytes } doReturn 4
|
||||||
|
on { txBytes } doReturn 40
|
||||||
|
}
|
||||||
|
|
||||||
|
val CYCLE3_AND_4_BUCKET = mock<Bucket> {
|
||||||
|
on { rxBytes } doReturn 7
|
||||||
|
on { txBytes } doReturn 70
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user