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:
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user