Fix DataUsageSummaryPreferenceController ANR
By off load data loading to background. Fix: 295260929 Test: manual - on Mobile Settings Test: unit test Change-Id: Ib2ef19301b1e97af8a7f3861829779c3b70da4a4
This commit is contained in:
@@ -18,8 +18,8 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="22dp"
|
||||
android:paddingBottom="32dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:orientation="vertical"
|
||||
@@ -99,6 +99,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="12dp"
|
||||
android:minHeight="54dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
|
50
src/com/android/settings/datausage/DataPlanInfo.kt
Normal file
50
src/com/android/settings/datausage/DataPlanInfo.kt
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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
|
||||
|
||||
data class DataPlanInfo(
|
||||
|
||||
/** The number of registered plans, [0, N] */
|
||||
val dataPlanCount: Int,
|
||||
|
||||
/**
|
||||
* The size of the first registered plan if one exists or the size of the warning if it is set.
|
||||
*
|
||||
* Set to -1 if no plan information is available.
|
||||
*/
|
||||
val dataPlanSize: Long,
|
||||
|
||||
/**
|
||||
* The "size" of the data usage bar, i.e. the amount of data its rhs end represents.
|
||||
*
|
||||
* Set to -1 if not display a data usage bar.
|
||||
*/
|
||||
val dataBarSize: Long,
|
||||
|
||||
/** The number of bytes used since the start of the cycle. */
|
||||
val dataPlanUse: Long,
|
||||
|
||||
/**
|
||||
* The ending time of the billing cycle in ms since the epoch.
|
||||
*
|
||||
* Set to `null` if no cycle information is available.
|
||||
*/
|
||||
val cycleEnd: Long?,
|
||||
|
||||
/** The time of the last update in milliseconds since the epoch, or -1 if unknown. */
|
||||
val snapshotTime: Long,
|
||||
)
|
75
src/com/android/settings/datausage/DataPlanRepository.kt
Normal file
75
src/com/android/settings/datausage/DataPlanRepository.kt
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.net.NetworkPolicy
|
||||
import android.telephony.SubscriptionPlan
|
||||
import com.android.settings.datausage.lib.INetworkCycleDataRepository
|
||||
import com.android.settings.datausage.lib.NetworkCycleDataRepository.Companion.getCycles
|
||||
import com.android.settings.datausage.lib.NetworkStatsRepository
|
||||
|
||||
interface DataPlanRepository {
|
||||
fun getDataPlanInfo(policy: NetworkPolicy, plans: List<SubscriptionPlan>): DataPlanInfo
|
||||
}
|
||||
|
||||
class DataPlanRepositoryImpl(
|
||||
private val networkCycleDataRepository: INetworkCycleDataRepository,
|
||||
) : DataPlanRepository {
|
||||
override fun getDataPlanInfo(
|
||||
policy: NetworkPolicy,
|
||||
plans: List<SubscriptionPlan>,
|
||||
): DataPlanInfo {
|
||||
getPrimaryPlan(plans)?.let { primaryPlan ->
|
||||
val dataPlanSize = when (primaryPlan.dataLimitBytes) {
|
||||
SubscriptionPlan.BYTES_UNLIMITED -> SubscriptionPlan.BYTES_UNKNOWN
|
||||
else -> primaryPlan.dataLimitBytes
|
||||
}
|
||||
return DataPlanInfo(
|
||||
dataPlanCount = plans.size,
|
||||
dataPlanSize = dataPlanSize,
|
||||
dataBarSize = dataPlanSize,
|
||||
dataPlanUse = primaryPlan.dataUsageBytes,
|
||||
cycleEnd = primaryPlan.cycleRule.end?.toInstant()?.toEpochMilli(),
|
||||
snapshotTime = primaryPlan.dataUsageTime,
|
||||
)
|
||||
}
|
||||
|
||||
val cycle = policy.getCycles().firstOrNull()
|
||||
val dataUsage = networkCycleDataRepository.queryUsage(
|
||||
cycle ?: NetworkStatsRepository.AllTimeRange
|
||||
).usage
|
||||
return DataPlanInfo(
|
||||
dataPlanCount = 0,
|
||||
dataPlanSize = SubscriptionPlan.BYTES_UNKNOWN,
|
||||
dataBarSize = maxOf(dataUsage, policy.limitBytes, policy.warningBytes),
|
||||
dataPlanUse = dataUsage,
|
||||
cycleEnd = cycle?.upper,
|
||||
snapshotTime = SubscriptionPlan.TIME_UNKNOWN,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PETA = 1_000_000_000_000_000L
|
||||
|
||||
private fun getPrimaryPlan(plans: List<SubscriptionPlan>): SubscriptionPlan? =
|
||||
plans.firstOrNull()?.takeIf { plan ->
|
||||
plan.dataLimitBytes > 0 && validSize(plan.dataUsageBytes) && plan.cycleRule != null
|
||||
}
|
||||
|
||||
private fun validSize(value: Long): Boolean = value in 0L until PETA
|
||||
}
|
||||
}
|
@@ -20,7 +20,6 @@ import android.annotation.AttrRes;
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.icu.text.MessageFormat;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextUtils;
|
||||
@@ -32,13 +31,14 @@ import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.settingslib.net.DataUsageController;
|
||||
import com.android.settingslib.utils.StringUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -62,10 +62,9 @@ public class DataUsageSummaryPreference extends Preference {
|
||||
private CharSequence mEndLabel;
|
||||
|
||||
private int mNumPlans;
|
||||
/** The specified un-initialized value for cycle time */
|
||||
private static final long CYCLE_TIME_UNINITIAL_VALUE = 0;
|
||||
/** The ending time of the billing cycle in milliseconds since epoch. */
|
||||
private long mCycleEndTimeMs;
|
||||
@Nullable
|
||||
private Long mCycleEndTimeMs;
|
||||
/** The time of the last update in standard milliseconds since the epoch */
|
||||
private long mSnapshotTimeMs;
|
||||
/** Name of carrier, or null if not available */
|
||||
@@ -74,7 +73,6 @@ public class DataUsageSummaryPreference extends Preference {
|
||||
|
||||
/** Progress to display on ProgressBar */
|
||||
private float mProgress;
|
||||
private boolean mHasMobileData;
|
||||
|
||||
/**
|
||||
* The size of the first registered plan if one exists or the size of the warning if it is set.
|
||||
@@ -102,7 +100,10 @@ public class DataUsageSummaryPreference extends Preference {
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
public void setUsageInfo(long cycleEnd, long snapshotTime, CharSequence carrierName,
|
||||
/**
|
||||
* Sets the usage info.
|
||||
*/
|
||||
public void setUsageInfo(@Nullable Long cycleEnd, long snapshotTime, CharSequence carrierName,
|
||||
int numPlans) {
|
||||
mCycleEndTimeMs = cycleEnd;
|
||||
mSnapshotTimeMs = snapshotTime;
|
||||
@@ -124,15 +125,17 @@ public class DataUsageSummaryPreference extends Preference {
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
void setUsageNumbers(long used, long dataPlanSize, boolean hasMobileData) {
|
||||
/**
|
||||
* Sets the usage numbers.
|
||||
*/
|
||||
public void setUsageNumbers(long used, long dataPlanSize) {
|
||||
mDataplanUse = used;
|
||||
mDataplanSize = dataPlanSize;
|
||||
mHasMobileData = hasMobileData;
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
|
||||
ProgressBar bar = getProgressBar(holder);
|
||||
@@ -178,7 +181,7 @@ public class DataUsageSummaryPreference extends Preference {
|
||||
|
||||
final MeasurableLinearLayout layout = getLayout(holder);
|
||||
|
||||
if (mHasMobileData && mNumPlans >= 0 && mDataplanSize > 0L) {
|
||||
if (mDataplanSize > 0L) {
|
||||
TextView usageRemainingField = getDataRemaining(holder);
|
||||
long dataRemaining = mDataplanSize - mDataplanUse;
|
||||
if (dataRemaining >= 0) {
|
||||
@@ -204,7 +207,7 @@ public class DataUsageSummaryPreference extends Preference {
|
||||
TextView cycleTime = getCycleTime(holder);
|
||||
|
||||
// Takes zero as a special case which value is never set.
|
||||
if (mCycleEndTimeMs == CYCLE_TIME_UNINITIAL_VALUE) {
|
||||
if (mCycleEndTimeMs == null) {
|
||||
cycleTime.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
@@ -228,7 +231,7 @@ public class DataUsageSummaryPreference extends Preference {
|
||||
|
||||
|
||||
private void updateCarrierInfo(TextView carrierInfo) {
|
||||
if (mNumPlans > 0 && mSnapshotTimeMs >= 0L) {
|
||||
if (mSnapshotTimeMs >= 0L) {
|
||||
carrierInfo.setVisibility(View.VISIBLE);
|
||||
long updateAgeMillis = calculateTruncatedUpdateAge();
|
||||
|
||||
@@ -293,13 +296,6 @@ public class DataUsageSummaryPreference extends Preference {
|
||||
carrierInfo.setTypeface(typeface);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected long getHistoricalUsageLevel() {
|
||||
final DataUsageController controller = new DataUsageController(getContext());
|
||||
return controller.getHistoricalUsageLevel(
|
||||
new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected TextView getUsageTitle(PreferenceViewHolder holder) {
|
||||
return (TextView) holder.findViewById(R.id.usage_title);
|
||||
|
@@ -1,273 +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.content.Context;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.telephony.SubscriptionPlan;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.RecurrenceRule;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.internal.util.CollectionUtils;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.PreferenceControllerMixin;
|
||||
import com.android.settings.datausage.lib.DataUsageLib;
|
||||
import com.android.settings.network.ProxySubscriptionManager;
|
||||
import com.android.settings.network.telephony.TelephonyBasePreferenceController;
|
||||
import com.android.settingslib.net.DataUsageController;
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* This is the controller for a data usage header that retrieves carrier data from the new
|
||||
* subscriptions framework API if available. The controller reads subscription information from the
|
||||
* framework and falls back to legacy usage data if none are available.
|
||||
*/
|
||||
public class DataUsageSummaryPreferenceController extends TelephonyBasePreferenceController
|
||||
implements PreferenceControllerMixin {
|
||||
|
||||
private static final String TAG = "DataUsageController";
|
||||
private static final String KEY = "status_header";
|
||||
private static final long PETA = 1000000000000000L;
|
||||
|
||||
protected DataUsageController mDataUsageController;
|
||||
protected DataUsageInfoController mDataInfoController;
|
||||
private NetworkTemplate mDefaultTemplate;
|
||||
private boolean mHasMobileData;
|
||||
|
||||
/** Name of the carrier, or null if not available */
|
||||
private CharSequence mCarrierName;
|
||||
|
||||
/** The number of registered plans, [0,N] */
|
||||
private int mDataplanCount;
|
||||
|
||||
/** The time of the last update in milliseconds since the epoch, or -1 if unknown */
|
||||
private long mSnapshotTime;
|
||||
|
||||
/**
|
||||
* The size of the first registered plan if one exists or the size of the warning if it is set.
|
||||
* -1 if no information is available.
|
||||
*/
|
||||
private long mDataplanSize;
|
||||
/** The "size" of the data usage bar, i.e. the amount of data its rhs end represents */
|
||||
private long mDataBarSize;
|
||||
/** The number of bytes used since the start of the cycle. */
|
||||
private long mDataplanUse;
|
||||
/** The ending time of the billing cycle in ms since the epoch */
|
||||
private long mCycleEnd;
|
||||
|
||||
private Future<Long> mHistoricalUsageLevel;
|
||||
|
||||
public DataUsageSummaryPreferenceController(Activity activity, int subscriptionId) {
|
||||
super(activity, KEY);
|
||||
|
||||
init(subscriptionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize based on subscription ID provided
|
||||
* @param subscriptionId is the target subscriptionId
|
||||
*/
|
||||
public void init(int subscriptionId) {
|
||||
mSubId = subscriptionId;
|
||||
mHasMobileData = DataUsageUtils.hasMobileData(mContext);
|
||||
mDataUsageController = null;
|
||||
}
|
||||
|
||||
protected void updateConfiguration(Context context,
|
||||
int subscriptionId, SubscriptionInfo subInfo) {
|
||||
mDataUsageController = createDataUsageController(context);
|
||||
mDataUsageController.setSubscriptionId(subscriptionId);
|
||||
mDataInfoController = new DataUsageInfoController();
|
||||
|
||||
if (subInfo != null) {
|
||||
mDefaultTemplate = DataUsageLib.getMobileTemplate(context, subscriptionId);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
DataUsageController createDataUsageController(Context context) {
|
||||
return new DataUsageController(context);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
DataUsageSummaryPreferenceController(
|
||||
DataUsageController dataUsageController,
|
||||
DataUsageInfoController dataInfoController,
|
||||
NetworkTemplate defaultTemplate,
|
||||
Activity activity,
|
||||
int subscriptionId) {
|
||||
super(activity, KEY);
|
||||
mDataUsageController = dataUsageController;
|
||||
mDataInfoController = dataInfoController;
|
||||
mDefaultTemplate = defaultTemplate;
|
||||
mHasMobileData = true;
|
||||
mSubId = subscriptionId;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
List<SubscriptionPlan> getSubscriptionPlans(int subscriptionId) {
|
||||
return ProxySubscriptionManager.getInstance(mContext).get()
|
||||
.getSubscriptionPlans(subscriptionId);
|
||||
}
|
||||
|
||||
protected SubscriptionInfo getSubscriptionInfo(int subscriptionId) {
|
||||
if (!mHasMobileData) {
|
||||
return null;
|
||||
}
|
||||
return ProxySubscriptionManager.getInstance(mContext)
|
||||
.getAccessibleSubscriptionInfo(subscriptionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus(int subId) {
|
||||
return getSubscriptionInfo(subId) != null ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(Preference preference) {
|
||||
DataUsageSummaryPreference summaryPreference = (DataUsageSummaryPreference) preference;
|
||||
|
||||
final SubscriptionInfo subInfo = getSubscriptionInfo(mSubId);
|
||||
if (subInfo == null) {
|
||||
return;
|
||||
}
|
||||
if (mDataUsageController == null) {
|
||||
updateConfiguration(mContext, mSubId, subInfo);
|
||||
}
|
||||
|
||||
mHistoricalUsageLevel = ThreadUtils.postOnBackgroundThread(() ->
|
||||
mDataUsageController.getHistoricalUsageLevel(mDefaultTemplate));
|
||||
|
||||
final DataUsageController.DataUsageInfo info =
|
||||
mDataUsageController.getDataUsageInfo(mDefaultTemplate);
|
||||
|
||||
long usageLevel = info.usageLevel;
|
||||
|
||||
refreshDataplanInfo(info, subInfo);
|
||||
|
||||
if (info.warningLevel > 0 && info.limitLevel > 0) {
|
||||
summaryPreference.setLimitInfo(TextUtils.expandTemplate(
|
||||
mContext.getText(R.string.cell_data_warning_and_limit),
|
||||
DataUsageUtils.formatDataUsage(mContext, info.warningLevel),
|
||||
DataUsageUtils.formatDataUsage(mContext, info.limitLevel)));
|
||||
} else if (info.warningLevel > 0) {
|
||||
summaryPreference.setLimitInfo(TextUtils.expandTemplate(
|
||||
mContext.getText(R.string.cell_data_warning),
|
||||
DataUsageUtils.formatDataUsage(mContext, info.warningLevel)));
|
||||
} else if (info.limitLevel > 0) {
|
||||
summaryPreference.setLimitInfo(TextUtils.expandTemplate(
|
||||
mContext.getText(R.string.cell_data_limit),
|
||||
DataUsageUtils.formatDataUsage(mContext, info.limitLevel)));
|
||||
} else {
|
||||
summaryPreference.setLimitInfo(null);
|
||||
}
|
||||
|
||||
if ((mDataplanUse <= 0L) && (mSnapshotTime < 0)) {
|
||||
Log.d(TAG, "Display data usage from history");
|
||||
mDataplanUse = displayUsageLevel(usageLevel);
|
||||
mSnapshotTime = -1L;
|
||||
}
|
||||
|
||||
summaryPreference.setUsageNumbers(mDataplanUse, mDataplanSize, mHasMobileData);
|
||||
|
||||
if (mDataBarSize <= 0) {
|
||||
summaryPreference.setChartEnabled(false);
|
||||
} else {
|
||||
summaryPreference.setChartEnabled(true);
|
||||
summaryPreference.setLabels(DataUsageUtils.formatDataUsage(mContext, 0 /* sizeBytes */),
|
||||
DataUsageUtils.formatDataUsage(mContext, mDataBarSize));
|
||||
summaryPreference.setProgress(mDataplanUse / (float) mDataBarSize);
|
||||
}
|
||||
summaryPreference.setUsageInfo(mCycleEnd, mSnapshotTime, mCarrierName, mDataplanCount);
|
||||
}
|
||||
|
||||
private long displayUsageLevel(long usageLevel) {
|
||||
if (usageLevel > 0) {
|
||||
return usageLevel;
|
||||
}
|
||||
try {
|
||||
usageLevel = mHistoricalUsageLevel.get();
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
return usageLevel;
|
||||
}
|
||||
|
||||
// TODO(b/70950124) add test for this method once the robolectric shadow run script is
|
||||
// completed (b/3526807)
|
||||
private void refreshDataplanInfo(DataUsageController.DataUsageInfo info,
|
||||
SubscriptionInfo subInfo) {
|
||||
// reset data before overwriting
|
||||
mCarrierName = null;
|
||||
mDataplanCount = 0;
|
||||
mDataplanSize = -1L;
|
||||
mDataBarSize = mDataInfoController.getSummaryLimit(info);
|
||||
mDataplanUse = info.usageLevel;
|
||||
mCycleEnd = info.cycleEnd;
|
||||
mSnapshotTime = -1L;
|
||||
|
||||
if (subInfo != null && mHasMobileData) {
|
||||
mCarrierName = subInfo.getCarrierName();
|
||||
final List<SubscriptionPlan> plans = getSubscriptionPlans(mSubId);
|
||||
final SubscriptionPlan primaryPlan = getPrimaryPlan(plans);
|
||||
|
||||
if (primaryPlan != null) {
|
||||
mDataplanCount = plans.size();
|
||||
mDataplanSize = primaryPlan.getDataLimitBytes();
|
||||
if (unlimited(mDataplanSize)) {
|
||||
mDataplanSize = -1L;
|
||||
}
|
||||
mDataBarSize = mDataplanSize;
|
||||
mDataplanUse = primaryPlan.getDataUsageBytes();
|
||||
|
||||
RecurrenceRule rule = primaryPlan.getCycleRule();
|
||||
if (rule != null && rule.start != null && rule.end != null) {
|
||||
mCycleEnd = rule.end.toEpochSecond() * 1000L;
|
||||
}
|
||||
mSnapshotTime = primaryPlan.getDataUsageTime();
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Have " + mDataplanCount + " plans, dflt sub-id " + mSubId);
|
||||
}
|
||||
|
||||
private static SubscriptionPlan getPrimaryPlan(List<SubscriptionPlan> plans) {
|
||||
if (CollectionUtils.isEmpty(plans)) {
|
||||
return null;
|
||||
}
|
||||
// First plan in the list is the primary plan
|
||||
SubscriptionPlan plan = plans.get(0);
|
||||
return plan.getDataLimitBytes() > 0
|
||||
&& validSize(plan.getDataUsageBytes())
|
||||
&& plan.getCycleRule() != null ? plan : null;
|
||||
}
|
||||
|
||||
private static boolean validSize(long value) {
|
||||
return value >= 0L && value < PETA;
|
||||
}
|
||||
|
||||
public static boolean unlimited(long size) {
|
||||
return size == SubscriptionPlan.BYTES_UNLIMITED;
|
||||
}
|
||||
}
|
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.NetworkPolicy
|
||||
import android.net.NetworkTemplate
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.preference.PreferenceScreen
|
||||
import com.android.settings.R
|
||||
import com.android.settings.datausage.lib.DataUsageLib.getMobileTemplate
|
||||
import com.android.settings.datausage.lib.INetworkCycleDataRepository
|
||||
import com.android.settings.datausage.lib.NetworkCycleDataRepository
|
||||
import com.android.settings.network.ProxySubscriptionManager
|
||||
import com.android.settings.network.telephony.TelephonyBasePreferenceController
|
||||
import kotlin.math.max
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* This is the controller for a data usage header that retrieves carrier data from the new
|
||||
* subscriptions framework API if available. The controller reads subscription information from the
|
||||
* framework and falls back to legacy usage data if none are available.
|
||||
*/
|
||||
open class DataUsageSummaryPreferenceController @JvmOverloads constructor(
|
||||
context: Context,
|
||||
subId: Int,
|
||||
private val proxySubscriptionManager: ProxySubscriptionManager =
|
||||
ProxySubscriptionManager.getInstance(context),
|
||||
private val networkCycleDataRepositoryFactory: (
|
||||
template: NetworkTemplate,
|
||||
) -> INetworkCycleDataRepository = { NetworkCycleDataRepository(context, it) },
|
||||
private val dataPlanRepositoryFactory: (
|
||||
networkCycleDataRepository: INetworkCycleDataRepository,
|
||||
) -> DataPlanRepository = { DataPlanRepositoryImpl(it) }
|
||||
) : TelephonyBasePreferenceController(context, KEY) {
|
||||
|
||||
init {
|
||||
mSubId = subId
|
||||
}
|
||||
|
||||
private val subInfo by lazy {
|
||||
if (DataUsageUtils.hasMobileData(mContext)) {
|
||||
proxySubscriptionManager.getAccessibleSubscriptionInfo(mSubId)
|
||||
} else null
|
||||
}
|
||||
private val networkCycleDataRepository by lazy {
|
||||
networkCycleDataRepositoryFactory(getMobileTemplate(mContext, mSubId))
|
||||
}
|
||||
private val policy by lazy { networkCycleDataRepository.getPolicy() }
|
||||
private lateinit var preference: DataUsageSummaryPreference
|
||||
|
||||
override fun getAvailabilityStatus(subId: Int) =
|
||||
if (subInfo != null && policy != null) AVAILABLE else CONDITIONALLY_UNAVAILABLE
|
||||
|
||||
override fun displayPreference(screen: PreferenceScreen) {
|
||||
super.displayPreference(screen)
|
||||
preference = screen.findPreference(preferenceKey)!!
|
||||
policy?.let {
|
||||
preference.setLimitInfo(it.getLimitInfo())
|
||||
val dataBarSize = max(it.limitBytes, it.warningBytes)
|
||||
if (dataBarSize > NetworkPolicy.WARNING_DISABLED) {
|
||||
setDataBarSize(dataBarSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
update()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun update() {
|
||||
val policy = policy ?: return
|
||||
val dataPlanInfo = withContext(Dispatchers.Default) {
|
||||
dataPlanRepositoryFactory(networkCycleDataRepository).getDataPlanInfo(
|
||||
policy = policy,
|
||||
plans = proxySubscriptionManager.get().getSubscriptionPlans(mSubId),
|
||||
)
|
||||
}
|
||||
Log.d(TAG, "dataPlanInfo: $dataPlanInfo")
|
||||
preference.setUsageNumbers(dataPlanInfo.dataPlanUse, dataPlanInfo.dataPlanSize)
|
||||
if (dataPlanInfo.dataBarSize > 0) {
|
||||
preference.setChartEnabled(true)
|
||||
setDataBarSize(dataPlanInfo.dataBarSize)
|
||||
preference.setProgress(dataPlanInfo.dataPlanUse / dataPlanInfo.dataBarSize.toFloat())
|
||||
} else {
|
||||
preference.setChartEnabled(false)
|
||||
}
|
||||
|
||||
preference.setUsageInfo(
|
||||
dataPlanInfo.cycleEnd,
|
||||
dataPlanInfo.snapshotTime,
|
||||
subInfo?.carrierName,
|
||||
dataPlanInfo.dataPlanCount,
|
||||
)
|
||||
}
|
||||
|
||||
private fun setDataBarSize(dataBarSize: Long) {
|
||||
preference.setLabels(
|
||||
DataUsageUtils.formatDataUsage(mContext, /* byteValue = */ 0),
|
||||
DataUsageUtils.formatDataUsage(mContext, dataBarSize)
|
||||
)
|
||||
}
|
||||
|
||||
private fun NetworkPolicy.getLimitInfo(): CharSequence? = when {
|
||||
warningBytes > 0 && limitBytes > 0 -> {
|
||||
TextUtils.expandTemplate(
|
||||
mContext.getText(R.string.cell_data_warning_and_limit),
|
||||
DataUsageUtils.formatDataUsage(mContext, warningBytes),
|
||||
DataUsageUtils.formatDataUsage(mContext, limitBytes),
|
||||
)
|
||||
}
|
||||
|
||||
warningBytes > 0 -> {
|
||||
TextUtils.expandTemplate(
|
||||
mContext.getText(R.string.cell_data_warning),
|
||||
DataUsageUtils.formatDataUsage(mContext, warningBytes),
|
||||
)
|
||||
}
|
||||
|
||||
limitBytes > 0 -> {
|
||||
TextUtils.expandTemplate(
|
||||
mContext.getText(R.string.cell_data_limit),
|
||||
DataUsageUtils.formatDataUsage(mContext, limitBytes),
|
||||
)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "DataUsageSummaryPC"
|
||||
private const val KEY = "status_header"
|
||||
}
|
||||
}
|
@@ -21,6 +21,7 @@ import android.net.NetworkTemplate
|
||||
import android.text.format.DateUtils
|
||||
import android.util.Range
|
||||
import com.android.settings.datausage.lib.NetworkCycleDataRepository.Companion.bucketRange
|
||||
import com.android.settings.datausage.lib.NetworkCycleDataRepository.Companion.getCycles
|
||||
import com.android.settings.datausage.lib.NetworkCycleDataRepository.Companion.reverseBucketRange
|
||||
import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.Bucket
|
||||
import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.aggregate
|
||||
@@ -37,12 +38,8 @@ class NetworkCycleBucketRepository(
|
||||
fun loadCycles(): List<NetworkUsageData> =
|
||||
getCycles().map { aggregateUsage(it) }.filter { it.usage > 0 }
|
||||
|
||||
private fun getCycles(): List<Range<Long>> {
|
||||
val policy = networkCycleDataRepository.getPolicy() ?: return queryCyclesAsFourWeeks()
|
||||
return policy.cycleIterator().asSequence().map {
|
||||
Range(it.lower.toInstant().toEpochMilli(), it.upper.toInstant().toEpochMilli())
|
||||
}.toList()
|
||||
}
|
||||
private fun getCycles(): List<Range<Long>> =
|
||||
networkCycleDataRepository.getPolicy()?.getCycles() ?: queryCyclesAsFourWeeks()
|
||||
|
||||
private fun queryCyclesAsFourWeeks(): List<Range<Long>> {
|
||||
val timeRange = buckets.aggregate()?.timeRange ?: return emptyList()
|
||||
|
@@ -27,6 +27,7 @@ import com.android.settingslib.NetworkPolicyEditor
|
||||
interface INetworkCycleDataRepository {
|
||||
fun getCycles(): List<Range<Long>>
|
||||
fun getPolicy(): NetworkPolicy?
|
||||
fun queryUsage(range: Range<Long>): NetworkUsageData
|
||||
}
|
||||
|
||||
class NetworkCycleDataRepository(
|
||||
@@ -40,12 +41,8 @@ class NetworkCycleDataRepository(
|
||||
|
||||
fun loadFirstCycle(): NetworkUsageData? = getCycles().firstOrNull()?.let { queryUsage(it) }
|
||||
|
||||
override 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()
|
||||
}
|
||||
override fun getCycles(): List<Range<Long>> =
|
||||
getPolicy()?.getCycles() ?: queryCyclesAsFourWeeks()
|
||||
|
||||
private fun queryCyclesAsFourWeeks(): List<Range<Long>> {
|
||||
val timeRange = networkStatsRepository.getTimeRange() ?: return emptyList()
|
||||
@@ -63,13 +60,17 @@ class NetworkCycleDataRepository(
|
||||
}
|
||||
|
||||
|
||||
fun queryUsage(range: Range<Long>) = NetworkUsageData(
|
||||
override fun queryUsage(range: Range<Long>) = NetworkUsageData(
|
||||
startTime = range.lower,
|
||||
endTime = range.upper,
|
||||
usage = networkStatsRepository.querySummaryForDevice(range.lower, range.upper),
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun NetworkPolicy.getCycles() = cycleIterator().asSequence().map {
|
||||
Range(it.lower.toInstant().toEpochMilli(), it.upper.toInstant().toEpochMilli())
|
||||
}.toList()
|
||||
|
||||
fun bucketRange(startTime: Long, endTime: Long, step: Long): List<Range<Long>> =
|
||||
(startTime..endTime step step).zipWithNext(::Range)
|
||||
|
||||
|
@@ -172,7 +172,7 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme
|
||||
});
|
||||
|
||||
return Arrays.asList(
|
||||
new DataUsageSummaryPreferenceController(getActivity(), mSubId),
|
||||
new DataUsageSummaryPreferenceController(context, mSubId),
|
||||
new RoamingPreferenceController(context, KEY_ROAMING_PREF, getSettingsLifecycle(),
|
||||
this, mSubId),
|
||||
new CallsDefaultSubscriptionController(context, KEY_CALLS_PREF,
|
||||
@@ -229,11 +229,6 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme
|
||||
|
||||
}
|
||||
|
||||
final DataUsageSummaryPreferenceController dataUsageSummaryPreferenceController =
|
||||
use(DataUsageSummaryPreferenceController.class);
|
||||
if (dataUsageSummaryPreferenceController != null) {
|
||||
dataUsageSummaryPreferenceController.init(mSubId);
|
||||
}
|
||||
use(MobileNetworkSwitchController.class).init(mSubId);
|
||||
use(CarrierSettingsVersionPreferenceController.class).init(mSubId);
|
||||
use(BillingCyclePreferenceController.class).init(mSubId);
|
||||
|
@@ -1,352 +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.android.settings.core.BasePreferenceController.AVAILABLE;
|
||||
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.SubscriptionPlan;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.RecurrenceRule;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.android.internal.logging.nano.MetricsProto;
|
||||
import com.android.settings.testutils.FakeFeatureFactory;
|
||||
import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
|
||||
import com.android.settings.widget.EntityHeaderController;
|
||||
import com.android.settingslib.net.DataUsageController;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = ShadowEntityHeaderController.class)
|
||||
public class DataUsageSummaryPreferenceControllerTest {
|
||||
|
||||
private static final long UPDATE_BACKOFF_MS = TimeUnit.MINUTES.toMillis(13);
|
||||
private static final long CYCLE_BACKOFF_MS = TimeUnit.DAYS.toMillis(6);
|
||||
private static final long CYCLE_LENGTH_MS = TimeUnit.DAYS.toMillis(30);
|
||||
private static final long USAGE1 = 373 * BillingCycleSettings.MIB_IN_BYTES;
|
||||
private static final long LIMIT1 = BillingCycleSettings.GIB_IN_BYTES;
|
||||
private static final String CARRIER_NAME = "z-mobile";
|
||||
private static final String PERIOD = "Feb";
|
||||
|
||||
@Mock
|
||||
private DataUsageController mDataUsageController;
|
||||
@Mock
|
||||
private DataUsageSummaryPreference mSummaryPreference;
|
||||
@Mock
|
||||
private NetworkTemplate mNetworkTemplate;
|
||||
@Mock
|
||||
private SubscriptionInfo mSubscriptionInfo;
|
||||
@Mock
|
||||
private SubscriptionPlan mSubscriptionPlan;
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private EntityHeaderController mHeaderController;
|
||||
@Mock
|
||||
private TelephonyManager mTelephonyManager;
|
||||
@Mock
|
||||
private PackageManager mPm;
|
||||
|
||||
private DataUsageInfoController mDataInfoController;
|
||||
|
||||
private FakeFeatureFactory mFactory;
|
||||
private FragmentActivity mActivity;
|
||||
private Context mContext;
|
||||
private DataUsageSummaryPreferenceController mController;
|
||||
private int mDefaultSubscriptionId;
|
||||
private List<SubscriptionPlan> mSubscriptionPlans;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
|
||||
doReturn("%1$s %2%s").when(mContext)
|
||||
.getString(com.android.internal.R.string.fileSizeSuffix);
|
||||
|
||||
mDefaultSubscriptionId = 1234;
|
||||
mSubscriptionPlans = new ArrayList<SubscriptionPlan>();
|
||||
|
||||
mFactory = FakeFeatureFactory.setupForTest();
|
||||
when(mFactory.metricsFeatureProvider.getMetricsCategory(any(Object.class)))
|
||||
.thenReturn(MetricsProto.MetricsEvent.SETTINGS_APP_NOTIF_CATEGORY);
|
||||
ShadowEntityHeaderController.setUseMock(mHeaderController);
|
||||
|
||||
mDataInfoController = spy(new DataUsageInfoController());
|
||||
doReturn(-1L).when(mDataInfoController).getSummaryLimit(any());
|
||||
|
||||
mActivity = spy(Robolectric.buildActivity(FragmentActivity.class).get());
|
||||
doReturn(mTelephonyManager).when(mActivity).getSystemService(TelephonyManager.class);
|
||||
doReturn(mTelephonyManager).when(mTelephonyManager)
|
||||
.createForSubscriptionId(mDefaultSubscriptionId);
|
||||
doReturn(mPm).when(mActivity).getPackageManager();
|
||||
doReturn(TelephonyManager.SIM_STATE_READY).when(mTelephonyManager).getSimState();
|
||||
|
||||
mController = spy(new DataUsageSummaryPreferenceController(
|
||||
mDataUsageController,
|
||||
mDataInfoController,
|
||||
mNetworkTemplate,
|
||||
mActivity, mDefaultSubscriptionId));
|
||||
doReturn(null).when(mController).getSubscriptionInfo(
|
||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
|
||||
doReturn(null).when(mController).getSubscriptionPlans(
|
||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
|
||||
|
||||
doReturn(CARRIER_NAME).when(mSubscriptionInfo).getCarrierName();
|
||||
doReturn(mSubscriptionInfo).when(mController).getSubscriptionInfo(mDefaultSubscriptionId);
|
||||
doReturn(mSubscriptionPlans).when(mController).getSubscriptionPlans(mDefaultSubscriptionId);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
ShadowEntityHeaderController.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSummaryUpdate_onePlan_basic() {
|
||||
final long now = System.currentTimeMillis();
|
||||
final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
|
||||
|
||||
doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
|
||||
setupTestDataUsage(LIMIT1, USAGE1, now - UPDATE_BACKOFF_MS);
|
||||
createTestDataPlan(info.cycleStart, info.cycleEnd);
|
||||
|
||||
mController.updateState(mSummaryPreference);
|
||||
|
||||
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
|
||||
verify(mSummaryPreference).setLimitInfo(captor.capture());
|
||||
CharSequence value = captor.getValue();
|
||||
assertThat(value.toString()).isEqualTo("512 MB data warning / 1.00 GB data limit");
|
||||
|
||||
// TODO (b/170330084): return intent instead of null for mSummaryPreference
|
||||
verify(mSummaryPreference).setUsageInfo((info.cycleEnd / 1000) * 1000,
|
||||
now - UPDATE_BACKOFF_MS,
|
||||
CARRIER_NAME, 1 /* numPlans */);
|
||||
verify(mSummaryPreference).setChartEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSummaryUpdate_noPlan_basic() {
|
||||
final long now = System.currentTimeMillis();
|
||||
final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
|
||||
|
||||
doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
|
||||
setupTestDataUsage(LIMIT1, USAGE1, now - UPDATE_BACKOFF_MS);
|
||||
|
||||
mController.updateState(mSummaryPreference);
|
||||
|
||||
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
|
||||
verify(mSummaryPreference).setLimitInfo(captor.capture());
|
||||
CharSequence value = captor.getValue();
|
||||
assertThat(value.toString()).isEqualTo("512 MB data warning / 1.00 GB data limit");
|
||||
|
||||
verify(mSummaryPreference).setUsageInfo(
|
||||
info.cycleEnd,
|
||||
-1L /* snapshotTime */,
|
||||
CARRIER_NAME,
|
||||
0 /* numPlans */);
|
||||
verify(mSummaryPreference).setChartEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSummaryUpdate_noCarrier_basic() {
|
||||
final long now = System.currentTimeMillis();
|
||||
final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
|
||||
|
||||
doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
|
||||
doReturn(null).when(mSubscriptionInfo).getCarrierName();
|
||||
setupTestDataUsage(LIMIT1, USAGE1, -1L /* snapshotTime */);
|
||||
|
||||
mController.updateState(mSummaryPreference);
|
||||
|
||||
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
|
||||
verify(mSummaryPreference).setLimitInfo(captor.capture());
|
||||
CharSequence value = captor.getValue();
|
||||
assertThat(value.toString()).isEqualTo("512 MB data warning / 1.00 GB data limit");
|
||||
|
||||
verify(mSummaryPreference).setUsageInfo(
|
||||
info.cycleEnd,
|
||||
-1L /* snapshotTime */,
|
||||
null /* carrierName */,
|
||||
0 /* numPlans */);
|
||||
verify(mSummaryPreference).setChartEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSummaryUpdate_noPlanData_basic() {
|
||||
final long now = System.currentTimeMillis();
|
||||
|
||||
final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
|
||||
|
||||
doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
|
||||
doReturn(null).when(mSubscriptionInfo).getCarrierName();
|
||||
setupTestDataUsage(-1L /* dataPlanSize */, USAGE1, -1L /* snapshotTime */);
|
||||
|
||||
mController.updateState(mSummaryPreference);
|
||||
|
||||
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
|
||||
verify(mSummaryPreference).setLimitInfo(captor.capture());
|
||||
CharSequence value = captor.getValue();
|
||||
assertThat(value.toString()).isEqualTo("512 MB data warning / 1.00 GB data limit");
|
||||
verify(mSummaryPreference).setUsageInfo(
|
||||
info.cycleEnd,
|
||||
-1L /* snapshotTime */,
|
||||
null /* carrierName */,
|
||||
0 /* numPlans */);
|
||||
verify(mSummaryPreference).setChartEnabled(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSummaryUpdate_noLimitNoWarning() {
|
||||
final long now = System.currentTimeMillis();
|
||||
final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
|
||||
info.warningLevel = 0L;
|
||||
info.limitLevel = 0L;
|
||||
|
||||
doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
|
||||
setupTestDataUsage(LIMIT1, USAGE1, now - UPDATE_BACKOFF_MS);
|
||||
|
||||
mController.updateState(mSummaryPreference);
|
||||
verify(mSummaryPreference).setLimitInfo(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSummaryUpdate_warningOnly() {
|
||||
final long now = System.currentTimeMillis();
|
||||
final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
|
||||
info.warningLevel = BillingCycleSettings.MIB_IN_BYTES;
|
||||
info.limitLevel = 0L;
|
||||
|
||||
doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
|
||||
setupTestDataUsage(LIMIT1, USAGE1, now - UPDATE_BACKOFF_MS);
|
||||
|
||||
mController.updateState(mSummaryPreference);
|
||||
|
||||
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
|
||||
verify(mSummaryPreference).setLimitInfo(captor.capture());
|
||||
CharSequence value = captor.getValue();
|
||||
assertThat(value.toString()).isEqualTo("1.00 MB data warning");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSummaryUpdate_limitOnly() {
|
||||
final long now = System.currentTimeMillis();
|
||||
final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
|
||||
info.warningLevel = 0L;
|
||||
info.limitLevel = BillingCycleSettings.MIB_IN_BYTES;
|
||||
|
||||
doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
|
||||
setupTestDataUsage(LIMIT1, USAGE1, now - UPDATE_BACKOFF_MS);
|
||||
|
||||
mController.updateState(mSummaryPreference);
|
||||
|
||||
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
|
||||
verify(mSummaryPreference).setLimitInfo(captor.capture());
|
||||
CharSequence value = captor.getValue();
|
||||
assertThat(value.toString()).isEqualTo("1.00 MB data limit");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSummaryUpdate_limitAndWarning() {
|
||||
final long now = System.currentTimeMillis();
|
||||
final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
|
||||
info.warningLevel = BillingCycleSettings.MIB_IN_BYTES;
|
||||
info.limitLevel = BillingCycleSettings.MIB_IN_BYTES;
|
||||
|
||||
doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
|
||||
setupTestDataUsage(LIMIT1, USAGE1, now - UPDATE_BACKOFF_MS);
|
||||
|
||||
mController.updateState(mSummaryPreference);
|
||||
|
||||
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
|
||||
verify(mSummaryPreference).setLimitInfo(captor.capture());
|
||||
CharSequence value = captor.getValue();
|
||||
assertThat(value.toString()).isEqualTo("1.00 MB data warning / 1.00 MB data limit");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMobileData_preferenceAvailable() {
|
||||
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMobileData_noSim_preferenceDisabled() {
|
||||
final int subscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
||||
mController.init(subscriptionId);
|
||||
mController.mDataUsageController = mDataUsageController;
|
||||
assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
|
||||
}
|
||||
|
||||
private DataUsageController.DataUsageInfo createTestDataUsageInfo(long now) {
|
||||
DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
|
||||
info.carrier = CARRIER_NAME;
|
||||
info.period = PERIOD;
|
||||
info.startDate = now;
|
||||
info.limitLevel = LIMIT1;
|
||||
info.warningLevel = LIMIT1 >> 1;
|
||||
info.usageLevel = USAGE1;
|
||||
info.cycleStart = now - CYCLE_BACKOFF_MS;
|
||||
info.cycleEnd = info.cycleStart + CYCLE_LENGTH_MS;
|
||||
return info;
|
||||
}
|
||||
|
||||
private void setupTestDataUsage(long dataPlanSize, long dataUsageSize, long snapshotTime) {
|
||||
doReturn(dataPlanSize).when(mSubscriptionPlan).getDataLimitBytes();
|
||||
doReturn(dataUsageSize).when(mSubscriptionPlan).getDataUsageBytes();
|
||||
doReturn(snapshotTime).when(mSubscriptionPlan).getDataUsageTime();
|
||||
|
||||
doReturn(dataPlanSize).when(mDataInfoController).getSummaryLimit(any());
|
||||
}
|
||||
|
||||
private void createTestDataPlan(long startTime, long endTime) {
|
||||
final RecurrenceRule recurrenceRule = new RecurrenceRule(
|
||||
Instant.ofEpochMilli(startTime).atZone(ZoneId.systemDefault()),
|
||||
Instant.ofEpochMilli(endTime).atZone(ZoneId.systemDefault()),
|
||||
null);
|
||||
doReturn(recurrenceRule).when(mSubscriptionPlan).getCycleRule();
|
||||
mSubscriptionPlans.add(mSubscriptionPlan);
|
||||
}
|
||||
}
|
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.net.NetworkPolicy
|
||||
import android.telephony.SubscriptionPlan
|
||||
import android.util.Range
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settings.datausage.lib.INetworkCycleDataRepository
|
||||
import com.android.settings.datausage.lib.NetworkUsageData
|
||||
import com.android.settings.testutils.zonedDateTime
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DataPlanRepositoryTest {
|
||||
|
||||
private object FakeNetworkCycleDataRepository : INetworkCycleDataRepository {
|
||||
override fun getCycles(): List<Range<Long>> = emptyList()
|
||||
override fun getPolicy() = null
|
||||
|
||||
override fun queryUsage(range: Range<Long>) = NetworkUsageData(
|
||||
startTime = CYCLE_CYCLE_START_TIME,
|
||||
endTime = CYCLE_CYCLE_END_TIME,
|
||||
usage = CYCLE_BYTES,
|
||||
)
|
||||
}
|
||||
|
||||
private val repository = DataPlanRepositoryImpl(FakeNetworkCycleDataRepository)
|
||||
|
||||
private val policy = mock<NetworkPolicy> {
|
||||
on { cycleIterator() } doReturn listOf(
|
||||
Range(zonedDateTime(CYCLE_CYCLE_START_TIME), zonedDateTime(CYCLE_CYCLE_END_TIME)),
|
||||
).iterator()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getDataPlanInfo_hasSubscriptionPlan() {
|
||||
val dataPlanInfo = repository.getDataPlanInfo(policy, listOf(SUBSCRIPTION_PLAN))
|
||||
|
||||
assertThat(dataPlanInfo).isEqualTo(
|
||||
DataPlanInfo(
|
||||
dataPlanCount = 1,
|
||||
dataPlanSize = DATA_LIMIT_BYTES,
|
||||
dataBarSize = DATA_LIMIT_BYTES,
|
||||
dataPlanUse = DATA_USAGE_BYTES,
|
||||
cycleEnd = PLAN_CYCLE_END_TIME,
|
||||
snapshotTime = DATA_USAGE_TIME,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getDataPlanInfo_noSubscriptionPlan() {
|
||||
val dataPlanInfo = repository.getDataPlanInfo(policy, emptyList())
|
||||
|
||||
assertThat(dataPlanInfo).isEqualTo(
|
||||
DataPlanInfo(
|
||||
dataPlanCount = 0,
|
||||
dataPlanSize = SubscriptionPlan.BYTES_UNKNOWN,
|
||||
dataBarSize = CYCLE_BYTES,
|
||||
dataPlanUse = CYCLE_BYTES,
|
||||
cycleEnd = CYCLE_CYCLE_END_TIME,
|
||||
snapshotTime = SubscriptionPlan.TIME_UNKNOWN,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val CYCLE_CYCLE_START_TIME = 1L
|
||||
const val CYCLE_CYCLE_END_TIME = 2L
|
||||
const val CYCLE_BYTES = 11L
|
||||
|
||||
const val PLAN_CYCLE_START_TIME = 100L
|
||||
const val PLAN_CYCLE_END_TIME = 200L
|
||||
const val DATA_LIMIT_BYTES = 300L
|
||||
const val DATA_USAGE_BYTES = 400L
|
||||
const val DATA_USAGE_TIME = 500L
|
||||
|
||||
val SUBSCRIPTION_PLAN: SubscriptionPlan = SubscriptionPlan.Builder.createNonrecurring(
|
||||
zonedDateTime(PLAN_CYCLE_START_TIME),
|
||||
zonedDateTime(PLAN_CYCLE_END_TIME),
|
||||
).apply {
|
||||
setDataLimit(DATA_LIMIT_BYTES, SubscriptionPlan.LIMIT_BEHAVIOR_DISABLED)
|
||||
setDataUsage(DATA_USAGE_BYTES, DATA_USAGE_TIME)
|
||||
}.build()
|
||||
}
|
||||
}
|
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.NetworkPolicy
|
||||
import android.telephony.SubscriptionInfo
|
||||
import android.telephony.SubscriptionManager
|
||||
import android.telephony.SubscriptionPlan
|
||||
import android.telephony.TelephonyManager
|
||||
import android.util.Range
|
||||
import androidx.lifecycle.testing.TestLifecycleOwner
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settings.core.BasePreferenceController.AVAILABLE
|
||||
import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE
|
||||
import com.android.settings.datausage.lib.INetworkCycleDataRepository
|
||||
import com.android.settings.datausage.lib.NetworkUsageData
|
||||
import com.android.settings.network.ProxySubscriptionManager
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
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.any
|
||||
import org.mockito.kotlin.argumentCaptor
|
||||
import org.mockito.kotlin.clearInvocations
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.never
|
||||
import org.mockito.kotlin.spy
|
||||
import org.mockito.kotlin.stub
|
||||
import org.mockito.kotlin.verify
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DataUsageSummaryPreferenceControllerTest {
|
||||
|
||||
private var policy: NetworkPolicy? = mock<NetworkPolicy>()
|
||||
|
||||
private val mockTelephonyManager = mock<TelephonyManager> {
|
||||
on { isDataCapable } doReturn true
|
||||
}
|
||||
|
||||
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
|
||||
on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
|
||||
}
|
||||
|
||||
private val mockSubscriptionManager = mock<SubscriptionManager> {
|
||||
on { getSubscriptionPlans(any()) } doReturn emptyList()
|
||||
}
|
||||
|
||||
private val mockProxySubscriptionManager = mock<ProxySubscriptionManager> {
|
||||
on { get() } doReturn mockSubscriptionManager
|
||||
}
|
||||
|
||||
private val fakeNetworkCycleDataRepository = object : INetworkCycleDataRepository {
|
||||
override fun getCycles(): List<Range<Long>> = emptyList()
|
||||
override fun getPolicy() = policy
|
||||
override fun queryUsage(range: Range<Long>) = NetworkUsageData.AllZero
|
||||
}
|
||||
|
||||
private var dataPlanInfo = EMPTY_DATA_PLAN_INFO
|
||||
|
||||
private val fakeDataPlanRepository = object : DataPlanRepository {
|
||||
override fun getDataPlanInfo(policy: NetworkPolicy, plans: List<SubscriptionPlan>) =
|
||||
dataPlanInfo
|
||||
}
|
||||
|
||||
private val controller = DataUsageSummaryPreferenceController(
|
||||
context = context,
|
||||
subId = SUB_ID,
|
||||
proxySubscriptionManager = mockProxySubscriptionManager,
|
||||
networkCycleDataRepositoryFactory = { fakeNetworkCycleDataRepository },
|
||||
dataPlanRepositoryFactory = { fakeDataPlanRepository },
|
||||
)
|
||||
|
||||
private val preference = mock<DataUsageSummaryPreference> {
|
||||
on { key } doReturn controller.preferenceKey
|
||||
}
|
||||
private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
preferenceScreen.addPreference(preference)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getAvailabilityStatus_noMobileData_conditionallyUnavailable() {
|
||||
mockTelephonyManager.stub {
|
||||
on { isDataCapable } doReturn false
|
||||
}
|
||||
|
||||
val availabilityStatus = controller.getAvailabilityStatus(SUB_ID)
|
||||
|
||||
assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getAvailabilityStatus_hasSubInfoAndPolicy_available() {
|
||||
mockProxySubscriptionManager.stub {
|
||||
on { getAccessibleSubscriptionInfo(SUB_ID) } doReturn SubscriptionInfo.Builder().build()
|
||||
}
|
||||
|
||||
val availabilityStatus = controller.getAvailabilityStatus(SUB_ID)
|
||||
|
||||
assertThat(availabilityStatus).isEqualTo(AVAILABLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getAvailabilityStatus_noSubInfo_conditionallyUnavailable() {
|
||||
mockProxySubscriptionManager.stub {
|
||||
on { getAccessibleSubscriptionInfo(SUB_ID) } doReturn null
|
||||
}
|
||||
|
||||
val availabilityStatus = controller.getAvailabilityStatus(SUB_ID)
|
||||
|
||||
assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getAvailabilityStatus_noPolicy_conditionallyUnavailable() {
|
||||
policy = null
|
||||
|
||||
val availabilityStatus = controller.getAvailabilityStatus(SUB_ID)
|
||||
|
||||
assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun displayPreference_policyHasNoLimitInfo() {
|
||||
policy = mock<NetworkPolicy>().apply {
|
||||
warningBytes = NetworkPolicy.WARNING_DISABLED
|
||||
limitBytes = NetworkPolicy.LIMIT_DISABLED
|
||||
}
|
||||
|
||||
controller.displayPreference(preferenceScreen)
|
||||
|
||||
verify(preference).setLimitInfo(null)
|
||||
verify(preference, never()).setLabels(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun displayPreference_policyWarningOnly() {
|
||||
policy = mock<NetworkPolicy>().apply {
|
||||
warningBytes = 1L
|
||||
limitBytes = NetworkPolicy.LIMIT_DISABLED
|
||||
}
|
||||
|
||||
controller.displayPreference(preferenceScreen)
|
||||
|
||||
val limitInfo = argumentCaptor {
|
||||
verify(preference).setLimitInfo(capture())
|
||||
}.firstValue.toString()
|
||||
assertThat(limitInfo).isEqualTo("1 B data warning")
|
||||
verify(preference).setLabels("0 B", "1 B")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun displayPreference_policyLimitOnly() {
|
||||
policy = mock<NetworkPolicy>().apply {
|
||||
warningBytes = NetworkPolicy.WARNING_DISABLED
|
||||
limitBytes = 1L
|
||||
}
|
||||
|
||||
controller.displayPreference(preferenceScreen)
|
||||
|
||||
val limitInfo = argumentCaptor {
|
||||
verify(preference).setLimitInfo(capture())
|
||||
}.firstValue.toString()
|
||||
assertThat(limitInfo).isEqualTo("1 B data limit")
|
||||
verify(preference).setLabels("0 B", "1 B")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun displayPreference_policyHasWarningAndLimit() {
|
||||
policy = mock<NetworkPolicy>().apply {
|
||||
warningBytes = BillingCycleSettings.GIB_IN_BYTES / 2
|
||||
limitBytes = BillingCycleSettings.GIB_IN_BYTES
|
||||
}
|
||||
|
||||
controller.displayPreference(preferenceScreen)
|
||||
|
||||
val limitInfo = argumentCaptor {
|
||||
verify(preference).setLimitInfo(capture())
|
||||
}.firstValue.toString()
|
||||
assertThat(limitInfo).isEqualTo("512 MB data warning / 1.00 GB data limit")
|
||||
verify(preference).setLabels("0 B", "1.00 GB")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onViewCreated_emptyDataPlanInfo() = runBlocking {
|
||||
dataPlanInfo = EMPTY_DATA_PLAN_INFO
|
||||
controller.displayPreference(preferenceScreen)
|
||||
clearInvocations(preference)
|
||||
|
||||
controller.onViewCreated(TestLifecycleOwner())
|
||||
delay(100)
|
||||
|
||||
verify(preference).setUsageNumbers(
|
||||
EMPTY_DATA_PLAN_INFO.dataPlanUse,
|
||||
EMPTY_DATA_PLAN_INFO.dataPlanSize,
|
||||
)
|
||||
verify(preference).setChartEnabled(false)
|
||||
verify(preference).setUsageInfo(
|
||||
EMPTY_DATA_PLAN_INFO.cycleEnd,
|
||||
EMPTY_DATA_PLAN_INFO.snapshotTime,
|
||||
null,
|
||||
EMPTY_DATA_PLAN_INFO.dataPlanCount,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onViewCreated_positiveDataPlanInfo() = runBlocking {
|
||||
dataPlanInfo = POSITIVE_DATA_PLAN_INFO
|
||||
controller.displayPreference(preferenceScreen)
|
||||
clearInvocations(preference)
|
||||
|
||||
controller.onViewCreated(TestLifecycleOwner())
|
||||
delay(100)
|
||||
|
||||
verify(preference).setUsageNumbers(
|
||||
POSITIVE_DATA_PLAN_INFO.dataPlanUse,
|
||||
POSITIVE_DATA_PLAN_INFO.dataPlanSize,
|
||||
)
|
||||
verify(preference).setChartEnabled(true)
|
||||
verify(preference).setLabels("0 B", "9 B")
|
||||
val progress = argumentCaptor {
|
||||
verify(preference).setProgress(capture())
|
||||
}.firstValue
|
||||
assertThat(progress).isEqualTo(0.8888889f)
|
||||
verify(preference).setUsageInfo(
|
||||
POSITIVE_DATA_PLAN_INFO.cycleEnd,
|
||||
POSITIVE_DATA_PLAN_INFO.snapshotTime,
|
||||
null,
|
||||
POSITIVE_DATA_PLAN_INFO.dataPlanCount,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val SUB_ID = 1234
|
||||
val EMPTY_DATA_PLAN_INFO = DataPlanInfo(
|
||||
dataPlanCount = 0,
|
||||
dataPlanSize = SubscriptionPlan.BYTES_UNKNOWN,
|
||||
dataBarSize = SubscriptionPlan.BYTES_UNKNOWN,
|
||||
dataPlanUse = 0,
|
||||
cycleEnd = null,
|
||||
snapshotTime = SubscriptionPlan.TIME_UNKNOWN,
|
||||
)
|
||||
val POSITIVE_DATA_PLAN_INFO = DataPlanInfo(
|
||||
dataPlanCount = 0,
|
||||
dataPlanSize = 10L,
|
||||
dataBarSize = 9L,
|
||||
dataPlanUse = 8L,
|
||||
cycleEnd = 7L,
|
||||
snapshotTime = 6L,
|
||||
)
|
||||
}
|
||||
}
|
@@ -23,10 +23,8 @@ import android.text.format.DateUtils
|
||||
import android.util.Range
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settings.testutils.zonedDateTime
|
||||
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
|
||||
@@ -96,9 +94,6 @@ class NetworkCycleDataRepositoryTest {
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
|
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.testutils
|
||||
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
fun zonedDateTime(epochMilli: Long): ZonedDateTime? =
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), ZoneId.systemDefault())
|
@@ -17,6 +17,7 @@
|
||||
package com.android.settings.datausage;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -90,7 +91,7 @@ public class DataUsageSummaryPreferenceTest {
|
||||
|
||||
@Test
|
||||
public void testSetUsageInfo_withNoDataPlans_carrierInfoNotShown() {
|
||||
mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, FAKE_CARRIER, 0 /* numPlans */);
|
||||
mSummaryPreference.setUsageInfo(mCycleEnd, -1, FAKE_CARRIER, 0 /* numPlans */);
|
||||
|
||||
mSummaryPreference.onBindViewHolder(mHolder);
|
||||
assertThat(mSummaryPreference.getCarrierInfo(mHolder).getVisibility())
|
||||
@@ -197,7 +198,7 @@ public class DataUsageSummaryPreferenceTest {
|
||||
|
||||
@Test
|
||||
public void testSetUsageInfo_withNoDataPlans_usageTitleNotShown() {
|
||||
mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, FAKE_CARRIER, 0 /* numPlans */);
|
||||
mSummaryPreference.setUsageInfo(mCycleEnd, -1, FAKE_CARRIER, 0 /* numPlans */);
|
||||
|
||||
mSummaryPreference.onBindViewHolder(mHolder);
|
||||
assertThat(mSummaryPreference.getUsageTitle(mHolder).getVisibility()).isEqualTo(View.GONE);
|
||||
@@ -216,7 +217,7 @@ public class DataUsageSummaryPreferenceTest {
|
||||
public void testSetUsageInfo_cycleRemainingTimeIsLessOneDay() {
|
||||
// just under one day
|
||||
final long cycleEnd = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(23);
|
||||
mSummaryPreference.setUsageInfo(cycleEnd, mUpdateTime, FAKE_CARRIER, 0 /* numPlans */);
|
||||
mSummaryPreference.setUsageInfo(cycleEnd, -1, FAKE_CARRIER, 0 /* numPlans */);
|
||||
|
||||
mSummaryPreference.onBindViewHolder(mHolder);
|
||||
assertThat(mSummaryPreference.getCycleTime(mHolder).getVisibility())
|
||||
@@ -229,7 +230,7 @@ public class DataUsageSummaryPreferenceTest {
|
||||
@Test
|
||||
public void testSetUsageInfo_cycleRemainingTimeNegativeDaysLeft_shouldDisplayNoneLeft() {
|
||||
final long cycleEnd = System.currentTimeMillis() - 1L;
|
||||
mSummaryPreference.setUsageInfo(cycleEnd, mUpdateTime, FAKE_CARRIER, 0 /* numPlans */);
|
||||
mSummaryPreference.setUsageInfo(cycleEnd, -1, FAKE_CARRIER, 0 /* numPlans */);
|
||||
|
||||
mSummaryPreference.onBindViewHolder(mHolder);
|
||||
assertThat(mSummaryPreference.getCycleTime(mHolder).getVisibility())
|
||||
@@ -243,7 +244,7 @@ public class DataUsageSummaryPreferenceTest {
|
||||
final int daysLeft = 3;
|
||||
final long cycleEnd = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(daysLeft)
|
||||
+ TimeUnit.HOURS.toMillis(1);
|
||||
mSummaryPreference.setUsageInfo(cycleEnd, mUpdateTime, FAKE_CARRIER, 0 /* numPlans */);
|
||||
mSummaryPreference.setUsageInfo(cycleEnd, -1, FAKE_CARRIER, 0 /* numPlans */);
|
||||
|
||||
mSummaryPreference.onBindViewHolder(mHolder);
|
||||
assertThat(mSummaryPreference.getCycleTime(mHolder).getVisibility())
|
||||
@@ -329,8 +330,7 @@ public class DataUsageSummaryPreferenceTest {
|
||||
mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, FAKE_CARRIER, 1 /* numPlans */);
|
||||
mSummaryPreference.setUsageNumbers(
|
||||
BillingCycleSettings.MIB_IN_BYTES,
|
||||
10 * BillingCycleSettings.MIB_IN_BYTES,
|
||||
true /* hasMobileData */);
|
||||
10 * BillingCycleSettings.MIB_IN_BYTES);
|
||||
|
||||
mSummaryPreference.onBindViewHolder(mHolder);
|
||||
assertThat(mSummaryPreference.getDataUsed(mHolder).getText().toString())
|
||||
@@ -349,8 +349,7 @@ public class DataUsageSummaryPreferenceTest {
|
||||
mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, FAKE_CARRIER, 1 /* numPlans */);
|
||||
mSummaryPreference.setUsageNumbers(
|
||||
11 * BillingCycleSettings.MIB_IN_BYTES,
|
||||
10 * BillingCycleSettings.MIB_IN_BYTES,
|
||||
true /* hasMobileData */);
|
||||
10 * BillingCycleSettings.MIB_IN_BYTES);
|
||||
|
||||
mSummaryPreference.onBindViewHolder(mHolder);
|
||||
assertThat(mSummaryPreference.getDataUsed(mHolder).getText().toString())
|
||||
@@ -364,9 +363,9 @@ public class DataUsageSummaryPreferenceTest {
|
||||
|
||||
@Test
|
||||
public void testSetUsageInfo_withUsageInfo_dataUsageShown() {
|
||||
mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, FAKE_CARRIER, 0 /* numPlans */);
|
||||
mSummaryPreference.setUsageInfo(mCycleEnd, -1, FAKE_CARRIER, 0 /* numPlans */);
|
||||
mSummaryPreference.setUsageNumbers(
|
||||
BillingCycleSettings.MIB_IN_BYTES, -1L, true /* hasMobileData */);
|
||||
BillingCycleSettings.MIB_IN_BYTES, -1L);
|
||||
|
||||
mSummaryPreference.onBindViewHolder(mHolder);
|
||||
assertThat(mSummaryPreference.getDataUsed(mHolder).getText().toString())
|
||||
@@ -383,8 +382,7 @@ public class DataUsageSummaryPreferenceTest {
|
||||
mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, FAKE_CARRIER, 1 /* numPlans */);
|
||||
mSummaryPreference.setUsageNumbers(
|
||||
BillingCycleSettings.MIB_IN_BYTES,
|
||||
10 * BillingCycleSettings.MIB_IN_BYTES,
|
||||
true /* hasMobileData */);
|
||||
10 * BillingCycleSettings.MIB_IN_BYTES);
|
||||
|
||||
int data_used_formatted_id = ResourcesUtils.getResourcesId(
|
||||
mContext, "string", "data_used_formatted");
|
||||
|
Reference in New Issue
Block a user