Create AppDataUsageCycleController
To improve performance and better organization and testings. Fix: 240931350 Test: manual - on AppDataUsage Test: unit test Change-Id: I277133b55378a3445aceb826d771b14c0fc91e4a
This commit is contained in:
@@ -21,7 +21,8 @@
|
|||||||
android:title="@string/data_usage_app_summary_title">
|
android:title="@string/data_usage_app_summary_title">
|
||||||
|
|
||||||
<com.android.settings.datausage.SpinnerPreference
|
<com.android.settings.datausage.SpinnerPreference
|
||||||
android:key="cycle" />
|
android:key="cycle"
|
||||||
|
settings:controller="com.android.settings.datausage.AppDataUsageCycleController" />
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:key="app_data_usage_summary_category">
|
android:key="app_data_usage_summary_category">
|
||||||
|
@@ -17,6 +17,7 @@ package com.android.settings.datausage;
|
|||||||
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
|
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
|
||||||
|
|
||||||
import static com.android.settings.datausage.lib.AppDataUsageRepository.getAppUid;
|
import static com.android.settings.datausage.lib.AppDataUsageRepository.getAppUid;
|
||||||
|
import static com.android.settings.datausage.lib.AppDataUsageRepository.getAppUidList;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
@@ -32,15 +33,9 @@ import android.telephony.SubscriptionManager;
|
|||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import android.util.IconDrawableFactory;
|
import android.util.IconDrawableFactory;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Range;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.loader.app.LoaderManager;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.Preference.OnPreferenceChangeListener;
|
import androidx.preference.Preference.OnPreferenceChangeListener;
|
||||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||||
@@ -48,17 +43,19 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.applications.AppInfoBase;
|
import com.android.settings.applications.AppInfoBase;
|
||||||
|
import com.android.settings.datausage.lib.AppDataUsageDetailsRepository;
|
||||||
|
import com.android.settings.datausage.lib.NetworkUsageDetailsData;
|
||||||
import com.android.settings.network.SubscriptionUtil;
|
import com.android.settings.network.SubscriptionUtil;
|
||||||
import com.android.settings.widget.EntityHeaderController;
|
import com.android.settings.widget.EntityHeaderController;
|
||||||
import com.android.settingslib.AppItem;
|
import com.android.settingslib.AppItem;
|
||||||
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||||
import com.android.settingslib.RestrictedLockUtilsInternal;
|
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||||
import com.android.settingslib.RestrictedSwitchPreference;
|
import com.android.settingslib.RestrictedSwitchPreference;
|
||||||
import com.android.settingslib.net.NetworkCycleDataForUid;
|
|
||||||
import com.android.settingslib.net.NetworkCycleDataForUidLoader;
|
|
||||||
import com.android.settingslib.net.UidDetail;
|
import com.android.settingslib.net.UidDetail;
|
||||||
import com.android.settingslib.net.UidDetailProvider;
|
import com.android.settingslib.net.UidDetailProvider;
|
||||||
|
|
||||||
|
import kotlin.Unit;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -77,11 +74,8 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
|
|||||||
private static final String KEY_FOREGROUND_USAGE = "foreground_usage";
|
private static final String KEY_FOREGROUND_USAGE = "foreground_usage";
|
||||||
private static final String KEY_BACKGROUND_USAGE = "background_usage";
|
private static final String KEY_BACKGROUND_USAGE = "background_usage";
|
||||||
private static final String KEY_RESTRICT_BACKGROUND = "restrict_background";
|
private static final String KEY_RESTRICT_BACKGROUND = "restrict_background";
|
||||||
private static final String KEY_CYCLE = "cycle";
|
|
||||||
private static final String KEY_UNRESTRICTED_DATA = "unrestricted_data_saver";
|
private static final String KEY_UNRESTRICTED_DATA = "unrestricted_data_saver";
|
||||||
|
|
||||||
private static final int LOADER_APP_USAGE_DATA = 2;
|
|
||||||
|
|
||||||
private PackageManager mPackageManager;
|
private PackageManager mPackageManager;
|
||||||
private final ArraySet<String> mPackages = new ArraySet<>();
|
private final ArraySet<String> mPackages = new ArraySet<>();
|
||||||
private Preference mTotalUsage;
|
private Preference mTotalUsage;
|
||||||
@@ -94,14 +88,10 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
|
|||||||
CharSequence mLabel;
|
CharSequence mLabel;
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
String mPackageName;
|
String mPackageName;
|
||||||
private CycleAdapter mCycleAdapter;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private List<NetworkCycleDataForUid> mUsageData;
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
NetworkTemplate mTemplate;
|
NetworkTemplate mTemplate;
|
||||||
private AppItem mAppItem;
|
private AppItem mAppItem;
|
||||||
private SpinnerPreference mCycle;
|
|
||||||
private RestrictedSwitchPreference mUnrestrictedData;
|
private RestrictedSwitchPreference mUnrestrictedData;
|
||||||
private DataSaverBackend mDataSaverBackend;
|
private DataSaverBackend mDataSaverBackend;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
@@ -160,7 +150,8 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
|
|||||||
mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE);
|
mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE);
|
||||||
mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE);
|
mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE);
|
||||||
|
|
||||||
initCycle();
|
final List<Integer> uidList = getAppUidList(mAppItem.uids);
|
||||||
|
initCycle(uidList);
|
||||||
|
|
||||||
final UidDetailProvider uidDetailProvider = getUidDetailProvider();
|
final UidDetailProvider uidDetailProvider = getUidDetailProvider();
|
||||||
|
|
||||||
@@ -191,7 +182,7 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
|
|||||||
}
|
}
|
||||||
mDataSaverBackend = new DataSaverBackend(mContext);
|
mDataSaverBackend = new DataSaverBackend(mContext);
|
||||||
|
|
||||||
use(AppDataUsageListController.class).init(mAppItem.uids);
|
use(AppDataUsageListController.class).init(uidList);
|
||||||
} else {
|
} else {
|
||||||
final Context context = getActivity();
|
final Context context = getActivity();
|
||||||
final UidDetail uidDetail = uidDetailProvider.getUidDetail(mAppItem.key, true);
|
final UidDetail uidDetail = uidDetailProvider.getUidDetail(mAppItem.key, true);
|
||||||
@@ -207,11 +198,9 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onStart() {
|
||||||
super.onResume();
|
super.onStart();
|
||||||
// No animations will occur before:
|
// No animations will occur before bindData() initially updates the cycle.
|
||||||
// - LOADER_APP_USAGE_DATA initially updates the cycle
|
|
||||||
// - updatePrefs() initially updates the preference visibility
|
|
||||||
// This is mainly for the cycle spinner, because when the page is entered from the
|
// This is mainly for the cycle spinner, because when the page is entered from the
|
||||||
// AppInfoDashboardFragment, there is no way to know whether the cycle data is available
|
// AppInfoDashboardFragment, there is no way to know whether the cycle data is available
|
||||||
// before finished the async loading.
|
// before finished the async loading.
|
||||||
@@ -219,11 +208,14 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
|
|||||||
// setBackPreferenceListAnimatorIfLoaded().
|
// setBackPreferenceListAnimatorIfLoaded().
|
||||||
mIsLoading = true;
|
mIsLoading = true;
|
||||||
getListView().setItemAnimator(null);
|
getListView().setItemAnimator(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
if (mDataSaverBackend != null) {
|
if (mDataSaverBackend != null) {
|
||||||
mDataSaverBackend.addListener(this);
|
mDataSaverBackend.addListener(this);
|
||||||
}
|
}
|
||||||
LoaderManager.getInstance(this).restartLoader(LOADER_APP_USAGE_DATA, null /* args */,
|
|
||||||
mUidDataCallbacks);
|
|
||||||
updatePrefs();
|
updatePrefs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,14 +260,16 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
|
|||||||
return new UidDetailProvider(mContext);
|
return new UidDetailProvider(mContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initCycle() {
|
@VisibleForTesting
|
||||||
mCycle = findPreference(KEY_CYCLE);
|
void initCycle(List<Integer> uidList) {
|
||||||
mCycleAdapter = new CycleAdapter(mContext, mCycle);
|
var controller = use(AppDataUsageCycleController.class);
|
||||||
|
var repository = new AppDataUsageDetailsRepository(mContext, mTemplate, mCycles, uidList);
|
||||||
|
controller.init(repository, data -> {
|
||||||
|
bindData(data);
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
if (mCycles != null) {
|
if (mCycles != null) {
|
||||||
// If coming from a page like DataUsageList where already has a selected cycle, display
|
controller.setInitialCycles(mCycles, mSelectedCycle);
|
||||||
// that before loading to reduce flicker.
|
|
||||||
mCycleAdapter.setInitialCycleList(mCycles, mSelectedCycle);
|
|
||||||
mCycle.setHasCycles(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,22 +320,13 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void bindData(int position) {
|
void bindData(@NonNull NetworkUsageDetailsData data) {
|
||||||
final long backgroundBytes, foregroundBytes;
|
mIsLoading = false;
|
||||||
if (mUsageData == null || position >= mUsageData.size()) {
|
mTotalUsage.setSummary(DataUsageUtils.formatDataUsage(mContext, data.getTotalUsage()));
|
||||||
backgroundBytes = foregroundBytes = 0;
|
mForegroundUsage.setSummary(
|
||||||
mCycle.setHasCycles(false);
|
DataUsageUtils.formatDataUsage(mContext, data.getForegroundUsage()));
|
||||||
} else {
|
mBackgroundUsage.setSummary(
|
||||||
mCycle.setHasCycles(true);
|
DataUsageUtils.formatDataUsage(mContext, data.getBackgroundUsage()));
|
||||||
final NetworkCycleDataForUid data = mUsageData.get(position);
|
|
||||||
backgroundBytes = data.getBackgroudUsage();
|
|
||||||
foregroundBytes = data.getForegroudUsage();
|
|
||||||
}
|
|
||||||
final long totalBytes = backgroundBytes + foregroundBytes;
|
|
||||||
|
|
||||||
mTotalUsage.setSummary(DataUsageUtils.formatDataUsage(mContext, totalBytes));
|
|
||||||
mForegroundUsage.setSummary(DataUsageUtils.formatDataUsage(mContext, foregroundBytes));
|
|
||||||
mBackgroundUsage.setSummary(DataUsageUtils.formatDataUsage(mContext, backgroundBytes));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean getAppRestrictBackground() {
|
private boolean getAppRestrictBackground() {
|
||||||
@@ -391,71 +376,6 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
|
|||||||
return SettingsEnums.APP_DATA_USAGE;
|
return SettingsEnums.APP_DATA_USAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final AdapterView.OnItemSelectedListener mCycleListener =
|
|
||||||
new AdapterView.OnItemSelectedListener() {
|
|
||||||
@Override
|
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
bindData(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNothingSelected(AdapterView<?> parent) {
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
final LoaderManager.LoaderCallbacks<List<NetworkCycleDataForUid>> mUidDataCallbacks =
|
|
||||||
new LoaderManager.LoaderCallbacks<>() {
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public Loader<List<NetworkCycleDataForUid>> onCreateLoader(int id, Bundle args) {
|
|
||||||
final NetworkCycleDataForUidLoader.Builder<?> builder =
|
|
||||||
NetworkCycleDataForUidLoader.builder(mContext);
|
|
||||||
builder.setRetrieveDetail(true)
|
|
||||||
.setNetworkTemplate(mTemplate);
|
|
||||||
for (int i = 0; i < mAppItem.uids.size(); i++) {
|
|
||||||
builder.addUid(mAppItem.uids.keyAt(i));
|
|
||||||
}
|
|
||||||
if (mCycles != null) {
|
|
||||||
builder.setCycles(mCycles);
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(@NonNull Loader<List<NetworkCycleDataForUid>> loader,
|
|
||||||
List<NetworkCycleDataForUid> data) {
|
|
||||||
mUsageData = data;
|
|
||||||
mCycle.setOnItemSelectedListener(mCycleListener);
|
|
||||||
mCycleAdapter.updateCycleList(data.stream()
|
|
||||||
.map(cycle -> new Range<>(cycle.getStartTime(), cycle.getEndTime()))
|
|
||||||
.toList());
|
|
||||||
if (mSelectedCycle > 0L) {
|
|
||||||
final int numCycles = data.size();
|
|
||||||
int position = 0;
|
|
||||||
for (int i = 0; i < numCycles; i++) {
|
|
||||||
final NetworkCycleDataForUid cycleData = data.get(i);
|
|
||||||
if (cycleData.getEndTime() == mSelectedCycle) {
|
|
||||||
position = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (position > 0) {
|
|
||||||
mCycle.setSelection(position);
|
|
||||||
}
|
|
||||||
bindData(position);
|
|
||||||
} else {
|
|
||||||
bindData(0 /* position */);
|
|
||||||
}
|
|
||||||
mIsLoading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(@NonNull Loader<List<NetworkCycleDataForUid>> loader) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataSaverChanged(boolean isDataSaving) {
|
public void onDataSaverChanged(boolean isDataSaving) {
|
||||||
|
|
||||||
|
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.datausage
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
import com.android.settings.core.BasePreferenceController
|
||||||
|
import com.android.settings.datausage.lib.AppDataUsageDetailsRepository
|
||||||
|
import com.android.settings.datausage.lib.IAppDataUsageDetailsRepository
|
||||||
|
import com.android.settings.datausage.lib.NetworkUsageDetailsData
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class AppDataUsageCycleController(context: Context, preferenceKey: String) :
|
||||||
|
BasePreferenceController(context, preferenceKey) {
|
||||||
|
|
||||||
|
private lateinit var repository: IAppDataUsageDetailsRepository
|
||||||
|
private var onUsageDataUpdated: (NetworkUsageDetailsData) -> Unit = {}
|
||||||
|
private lateinit var preference: SpinnerPreference
|
||||||
|
private var cycleAdapter: CycleAdapter? = null
|
||||||
|
|
||||||
|
private var initialCycles: List<Long> = emptyList()
|
||||||
|
private var initialSelectedEndTime: Long = -1
|
||||||
|
|
||||||
|
private var usageDetailsDataList: List<NetworkUsageDetailsData> = emptyList()
|
||||||
|
|
||||||
|
fun init(
|
||||||
|
repository: IAppDataUsageDetailsRepository,
|
||||||
|
onUsageDataUpdated: (NetworkUsageDetailsData) -> Unit,
|
||||||
|
) {
|
||||||
|
this.repository = repository
|
||||||
|
this.onUsageDataUpdated = onUsageDataUpdated
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the initial cycles.
|
||||||
|
*
|
||||||
|
* If coming from a page like DataUsageList where already has a selected cycle, display that
|
||||||
|
* before loading to reduce flicker.
|
||||||
|
*/
|
||||||
|
fun setInitialCycles(initialCycles: List<Long>, initialSelectedEndTime: Long) {
|
||||||
|
this.initialCycles = initialCycles
|
||||||
|
this.initialSelectedEndTime = initialSelectedEndTime
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAvailabilityStatus() = AVAILABLE
|
||||||
|
|
||||||
|
override fun displayPreference(screen: PreferenceScreen) {
|
||||||
|
super.displayPreference(screen)
|
||||||
|
preference = screen.findPreference(preferenceKey)!!
|
||||||
|
if (cycleAdapter == null) {
|
||||||
|
cycleAdapter = CycleAdapter(mContext, preference).apply {
|
||||||
|
if (initialCycles.isNotEmpty()) {
|
||||||
|
setInitialCycleList(initialCycles, initialSelectedEndTime)
|
||||||
|
preference.setHasCycles(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun update() {
|
||||||
|
usageDetailsDataList = withContext(Dispatchers.Default) {
|
||||||
|
repository.queryDetailsForCycles()
|
||||||
|
}
|
||||||
|
if (usageDetailsDataList.isEmpty()) {
|
||||||
|
preference.setHasCycles(false)
|
||||||
|
onUsageDataUpdated(NetworkUsageDetailsData.AllZero)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
preference.setHasCycles(true)
|
||||||
|
cycleAdapter?.updateCycleList(usageDetailsDataList.map { it.range })
|
||||||
|
preference.setOnItemSelectedListener(cycleListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cycleListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
|
usageDetailsDataList.getOrNull(position)?.let(onUsageDataUpdated)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -28,6 +28,7 @@ import androidx.preference.PreferenceGroup
|
|||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import com.android.settings.core.BasePreferenceController
|
import com.android.settings.core.BasePreferenceController
|
||||||
import com.android.settings.datausage.lib.AppDataUsageRepository.Companion.getAppUid
|
import com.android.settings.datausage.lib.AppDataUsageRepository.Companion.getAppUid
|
||||||
|
import com.android.settings.datausage.lib.AppDataUsageRepository.Companion.getAppUidList
|
||||||
import com.android.settings.datausage.lib.AppPreferenceRepository
|
import com.android.settings.datausage.lib.AppPreferenceRepository
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -43,8 +44,8 @@ open class AppDataUsageListController @JvmOverloads constructor(
|
|||||||
private var uids: List<Int> = emptyList()
|
private var uids: List<Int> = emptyList()
|
||||||
private lateinit var preference: PreferenceGroup
|
private lateinit var preference: PreferenceGroup
|
||||||
|
|
||||||
fun init(uids: SparseBooleanArray) {
|
fun init(uids: List<Int>) {
|
||||||
this.uids = uids.keyIterator().asSequence().map { getAppUid(it) }.distinct().toList()
|
this.uids = uids
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAvailabilityStatus() = AVAILABLE
|
override fun getAvailabilityStatus() = AVAILABLE
|
||||||
|
@@ -94,7 +94,6 @@ public class SpinnerPreference extends Preference implements CycleAdapter.Spinne
|
|||||||
@Override
|
@Override
|
||||||
public void onItemSelected(
|
public void onItemSelected(
|
||||||
AdapterView<?> parent, View view, int position, long id) {
|
AdapterView<?> parent, View view, int position, long id) {
|
||||||
if (mPosition == position) return;
|
|
||||||
mPosition = position;
|
mPosition = position;
|
||||||
mCurrentObject = mAdapter.getItem(position);
|
mCurrentObject = mAdapter.getItem(position);
|
||||||
if (mListener != null) {
|
if (mListener != null) {
|
||||||
|
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.datausage.lib
|
||||||
|
|
||||||
|
import android.app.usage.NetworkStats
|
||||||
|
import android.app.usage.NetworkStatsManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.NetworkTemplate
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.Range
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
|
||||||
|
interface IAppDataUsageDetailsRepository {
|
||||||
|
suspend fun queryDetailsForCycles(): List<NetworkUsageDetailsData>
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppDataUsageDetailsRepository @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
private val template: NetworkTemplate,
|
||||||
|
private val cycles: List<Long>?,
|
||||||
|
private val uids: List<Int>,
|
||||||
|
private val networkCycleDataRepository: INetworkCycleDataRepository =
|
||||||
|
NetworkCycleDataRepository(context, template)
|
||||||
|
) : IAppDataUsageDetailsRepository {
|
||||||
|
private val networkStatsManager = context.getSystemService(NetworkStatsManager::class.java)!!
|
||||||
|
|
||||||
|
override suspend fun queryDetailsForCycles(): List<NetworkUsageDetailsData> = coroutineScope {
|
||||||
|
getCycles().map {
|
||||||
|
async {
|
||||||
|
queryDetails(it)
|
||||||
|
}
|
||||||
|
}.awaitAll().filter { it.totalUsage > 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCycles(): List<Range<Long>> =
|
||||||
|
cycles?.zipWithNext { endTime, startTime -> Range(startTime, endTime) }
|
||||||
|
?: networkCycleDataRepository.getCycles()
|
||||||
|
|
||||||
|
private fun queryDetails(range: Range<Long>): NetworkUsageDetailsData {
|
||||||
|
var totalUsage = 0L
|
||||||
|
var foregroundUsage = 0L
|
||||||
|
for (uid in uids) {
|
||||||
|
val usage = getUsage(range, uid, NetworkStats.Bucket.STATE_ALL)
|
||||||
|
if (usage > 0L) {
|
||||||
|
totalUsage += usage
|
||||||
|
foregroundUsage +=
|
||||||
|
getUsage(range, uid, NetworkStats.Bucket.STATE_FOREGROUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NetworkUsageDetailsData(
|
||||||
|
range = range,
|
||||||
|
totalUsage = totalUsage,
|
||||||
|
foregroundUsage = foregroundUsage,
|
||||||
|
backgroundUsage = totalUsage - foregroundUsage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
fun getUsage(range: Range<Long>, uid: Int, state: Int): Long = try {
|
||||||
|
networkStatsManager.queryDetailsForUidTagState(
|
||||||
|
template, range.lower, range.upper, uid, NetworkStats.Bucket.TAG_NONE, state,
|
||||||
|
).getTotalUsage()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Exception querying network detail.", e)
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NetworkStats.getTotalUsage(): Long = use {
|
||||||
|
var bytes = 0L
|
||||||
|
val bucket = NetworkStats.Bucket()
|
||||||
|
while (getNextBucket(bucket)) {
|
||||||
|
bytes += bucket.rxBytes + bucket.txBytes
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private const val TAG = "AppDataUsageDetailsRepo"
|
||||||
|
}
|
||||||
|
}
|
@@ -25,7 +25,9 @@ import android.os.Process
|
|||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.SparseArray
|
import android.util.SparseArray
|
||||||
|
import android.util.SparseBooleanArray
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import androidx.core.util.keyIterator
|
||||||
import com.android.settings.R
|
import com.android.settings.R
|
||||||
import com.android.settingslib.AppItem
|
import com.android.settingslib.AppItem
|
||||||
import com.android.settingslib.net.UidDetailProvider
|
import com.android.settingslib.net.UidDetailProvider
|
||||||
@@ -195,6 +197,10 @@ class AppDataUsageRepository(
|
|||||||
val bytes: Long,
|
val bytes: Long,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getAppUidList(uids: SparseBooleanArray) =
|
||||||
|
uids.keyIterator().asSequence().map { getAppUid(it) }.distinct().toList()
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getAppUid(uid: Int): Int {
|
fun getAppUid(uid: Int): Int {
|
||||||
if (Process.isSdkSandboxUid(uid)) {
|
if (Process.isSdkSandboxUid(uid)) {
|
||||||
|
@@ -33,6 +33,7 @@ import kotlinx.coroutines.coroutineScope
|
|||||||
|
|
||||||
interface INetworkCycleDataRepository {
|
interface INetworkCycleDataRepository {
|
||||||
suspend fun loadCycles(): List<NetworkUsageData>
|
suspend fun loadCycles(): List<NetworkUsageData>
|
||||||
|
fun getCycles(): List<Range<Long>>
|
||||||
fun getPolicy(): NetworkPolicy?
|
fun getPolicy(): NetworkPolicy?
|
||||||
suspend fun querySummary(startTime: Long, endTime: Long): NetworkCycleChartData?
|
suspend fun querySummary(startTime: Long, endTime: Long): NetworkCycleChartData?
|
||||||
}
|
}
|
||||||
@@ -48,7 +49,7 @@ class NetworkCycleDataRepository(
|
|||||||
override suspend fun loadCycles(): List<NetworkUsageData> =
|
override suspend fun loadCycles(): List<NetworkUsageData> =
|
||||||
getCycles().queryUsage().filter { it.usage > 0 }
|
getCycles().queryUsage().filter { it.usage > 0 }
|
||||||
|
|
||||||
private fun getCycles(): List<Range<Long>> {
|
override fun getCycles(): List<Range<Long>> {
|
||||||
val policy = getPolicy() ?: return queryCyclesAsFourWeeks()
|
val policy = getPolicy() ?: return queryCyclesAsFourWeeks()
|
||||||
return policy.cycleIterator().asSequence().map {
|
return policy.cycleIterator().asSequence().map {
|
||||||
Range(it.lower.toInstant().toEpochMilli(), it.upper.toInstant().toEpochMilli())
|
Range(it.lower.toInstant().toEpochMilli(), it.upper.toInstant().toEpochMilli())
|
||||||
|
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.datausage.lib
|
||||||
|
|
||||||
|
import android.util.Range
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details data structure representing usage data in a period.
|
||||||
|
*/
|
||||||
|
data class NetworkUsageDetailsData(
|
||||||
|
val range: Range<Long>,
|
||||||
|
val totalUsage: Long,
|
||||||
|
val foregroundUsage: Long,
|
||||||
|
val backgroundUsage: Long,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val AllZero = NetworkUsageDetailsData(
|
||||||
|
range = Range(0, 0),
|
||||||
|
totalUsage = 0,
|
||||||
|
foregroundUsage = 0,
|
||||||
|
backgroundUsage = 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -41,8 +41,8 @@ import android.net.NetworkTemplate;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.telephony.SubscriptionManager;
|
import android.telephony.SubscriptionManager;
|
||||||
import android.text.format.DateUtils;
|
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
|
import android.util.Range;
|
||||||
|
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
@@ -51,6 +51,7 @@ import androidx.preference.PreferenceScreen;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.android.settings.applications.AppInfoBase;
|
import com.android.settings.applications.AppInfoBase;
|
||||||
|
import com.android.settings.datausage.lib.NetworkUsageDetailsData;
|
||||||
import com.android.settings.testutils.FakeFeatureFactory;
|
import com.android.settings.testutils.FakeFeatureFactory;
|
||||||
import com.android.settings.testutils.shadow.ShadowDataUsageUtils;
|
import com.android.settings.testutils.shadow.ShadowDataUsageUtils;
|
||||||
import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
|
import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
|
||||||
@@ -61,8 +62,6 @@ import com.android.settingslib.AppItem;
|
|||||||
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||||
import com.android.settingslib.RestrictedSwitchPreference;
|
import com.android.settingslib.RestrictedSwitchPreference;
|
||||||
import com.android.settingslib.core.AbstractPreferenceController;
|
import com.android.settingslib.core.AbstractPreferenceController;
|
||||||
import com.android.settingslib.net.NetworkCycleDataForUid;
|
|
||||||
import com.android.settingslib.net.NetworkCycleDataForUidLoader;
|
|
||||||
import com.android.settingslib.net.UidDetail;
|
import com.android.settingslib.net.UidDetail;
|
||||||
import com.android.settingslib.net.UidDetailProvider;
|
import com.android.settingslib.net.UidDetailProvider;
|
||||||
|
|
||||||
@@ -80,7 +79,6 @@ import org.robolectric.annotation.Config;
|
|||||||
import org.robolectric.shadows.ShadowSubscriptionManager;
|
import org.robolectric.shadows.ShadowSubscriptionManager;
|
||||||
import org.robolectric.util.ReflectionHelpers;
|
import org.robolectric.util.ReflectionHelpers;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
@@ -254,163 +252,33 @@ public class AppDataUsageTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindData_noAppUsageData_shouldHideCycleSpinner() {
|
public void bindData_shouldUpdateUsageSummary() {
|
||||||
mFragment = spy(new TestFragment());
|
|
||||||
final SpinnerPreference cycle = mock(SpinnerPreference.class);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mCycle", cycle);
|
|
||||||
final Preference preference = mock(Preference.class);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mBackgroundUsage", preference);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mForegroundUsage", preference);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mTotalUsage", preference);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mContext", RuntimeEnvironment.application);
|
|
||||||
|
|
||||||
mFragment.bindData(0 /* position */);
|
|
||||||
|
|
||||||
verify(cycle).setHasCycles(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void bindData_hasAppUsageData_shouldShowCycleSpinnerAndUpdateUsageSummary() {
|
|
||||||
mFragment = spy(new TestFragment());
|
mFragment = spy(new TestFragment());
|
||||||
final Context context = RuntimeEnvironment.application;
|
final Context context = RuntimeEnvironment.application;
|
||||||
ReflectionHelpers.setField(mFragment, "mContext", context);
|
ReflectionHelpers.setField(mFragment, "mContext", context);
|
||||||
final long backgroundBytes = 1234L;
|
final long backgroundBytes = 1234L;
|
||||||
final long foregroundBytes = 5678L;
|
final long foregroundBytes = 5678L;
|
||||||
final List<NetworkCycleDataForUid> appUsage = new ArrayList<>();
|
final NetworkUsageDetailsData appUsage = new NetworkUsageDetailsData(
|
||||||
appUsage.add(new NetworkCycleDataForUid.Builder()
|
new Range<>(1L, 2L),
|
||||||
.setBackgroundUsage(backgroundBytes).setForegroundUsage(foregroundBytes).build());
|
backgroundBytes + foregroundBytes,
|
||||||
ReflectionHelpers.setField(mFragment, "mUsageData", appUsage);
|
foregroundBytes,
|
||||||
|
backgroundBytes
|
||||||
|
);
|
||||||
final Preference backgroundPref = mock(Preference.class);
|
final Preference backgroundPref = mock(Preference.class);
|
||||||
ReflectionHelpers.setField(mFragment, "mBackgroundUsage", backgroundPref);
|
ReflectionHelpers.setField(mFragment, "mBackgroundUsage", backgroundPref);
|
||||||
final Preference foregroundPref = mock(Preference.class);
|
final Preference foregroundPref = mock(Preference.class);
|
||||||
ReflectionHelpers.setField(mFragment, "mForegroundUsage", foregroundPref);
|
ReflectionHelpers.setField(mFragment, "mForegroundUsage", foregroundPref);
|
||||||
final Preference totalPref = mock(Preference.class);
|
final Preference totalPref = mock(Preference.class);
|
||||||
ReflectionHelpers.setField(mFragment, "mTotalUsage", totalPref);
|
ReflectionHelpers.setField(mFragment, "mTotalUsage", totalPref);
|
||||||
final SpinnerPreference cycle = mock(SpinnerPreference.class);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mCycle", cycle);
|
|
||||||
|
|
||||||
mFragment.bindData(0 /* position */);
|
mFragment.bindData(appUsage);
|
||||||
|
|
||||||
verify(cycle).setHasCycles(true);
|
|
||||||
verify(totalPref).setSummary(
|
verify(totalPref).setSummary(
|
||||||
DataUsageUtils.formatDataUsage(context, backgroundBytes + foregroundBytes));
|
DataUsageUtils.formatDataUsage(context, backgroundBytes + foregroundBytes));
|
||||||
verify(backgroundPref).setSummary(DataUsageUtils.formatDataUsage(context, backgroundBytes));
|
verify(backgroundPref).setSummary(DataUsageUtils.formatDataUsage(context, backgroundBytes));
|
||||||
verify(foregroundPref).setSummary(DataUsageUtils.formatDataUsage(context, foregroundBytes));
|
verify(foregroundPref).setSummary(DataUsageUtils.formatDataUsage(context, foregroundBytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onCreateLoader_categoryApp_shouldQueryDataUsageUsingAppKey() {
|
|
||||||
mFragment = new TestFragment();
|
|
||||||
final Context context = RuntimeEnvironment.application;
|
|
||||||
final int testUid = 123123;
|
|
||||||
final AppItem appItem = new AppItem(testUid);
|
|
||||||
appItem.addUid(testUid);
|
|
||||||
appItem.category = AppItem.CATEGORY_APP;
|
|
||||||
ReflectionHelpers.setField(mFragment, "mContext", context);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mAppItem", appItem);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mTemplate",
|
|
||||||
new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build());
|
|
||||||
final long end = System.currentTimeMillis();
|
|
||||||
final long start = end - (DateUtils.WEEK_IN_MILLIS * 4);
|
|
||||||
|
|
||||||
final NetworkCycleDataForUidLoader loader = (NetworkCycleDataForUidLoader)
|
|
||||||
mFragment.mUidDataCallbacks.onCreateLoader(0, Bundle.EMPTY);
|
|
||||||
|
|
||||||
final List<Integer> uids = loader.getUids();
|
|
||||||
assertThat(uids).hasSize(1);
|
|
||||||
assertThat(uids.get(0)).isEqualTo(testUid);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onCreateLoader_categoryUser_shouldQueryDataUsageUsingAssociatedUids() {
|
|
||||||
mFragment = new TestFragment();
|
|
||||||
final Context context = RuntimeEnvironment.application;
|
|
||||||
final int testUserId = 11;
|
|
||||||
final AppItem appItem = new AppItem(testUserId);
|
|
||||||
appItem.category = AppItem.CATEGORY_USER;
|
|
||||||
appItem.addUid(123);
|
|
||||||
appItem.addUid(456);
|
|
||||||
appItem.addUid(789);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mContext", context);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mAppItem", appItem);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mTemplate",
|
|
||||||
new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build());
|
|
||||||
final long end = System.currentTimeMillis();
|
|
||||||
final long start = end - (DateUtils.WEEK_IN_MILLIS * 4);
|
|
||||||
|
|
||||||
final NetworkCycleDataForUidLoader loader = (NetworkCycleDataForUidLoader)
|
|
||||||
mFragment.mUidDataCallbacks.onCreateLoader(0, Bundle.EMPTY);
|
|
||||||
|
|
||||||
final List<Integer> uids = loader.getUids();
|
|
||||||
assertThat(uids).hasSize(3);
|
|
||||||
assertThat(uids.get(0)).isEqualTo(123);
|
|
||||||
assertThat(uids.get(1)).isEqualTo(456);
|
|
||||||
assertThat(uids.get(2)).isEqualTo(789);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onCreateLoader_hasCyclesSpecified_shouldQueryDataUsageForSpecifiedCycles() {
|
|
||||||
final long startTime = 1521583200000L;
|
|
||||||
final long endTime = 1521676800000L;
|
|
||||||
ArrayList<Long> testCycles = new ArrayList<>();
|
|
||||||
testCycles.add(endTime);
|
|
||||||
testCycles.add(startTime);
|
|
||||||
final int uid = 123;
|
|
||||||
final AppItem appItem = new AppItem(uid);
|
|
||||||
appItem.category = AppItem.CATEGORY_APP;
|
|
||||||
appItem.addUid(uid);
|
|
||||||
|
|
||||||
mFragment = new TestFragment();
|
|
||||||
ReflectionHelpers.setField(mFragment, "mContext", RuntimeEnvironment.application);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mCycles", testCycles);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mAppItem", appItem);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mTemplate",
|
|
||||||
new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build());
|
|
||||||
|
|
||||||
final NetworkCycleDataForUidLoader loader = (NetworkCycleDataForUidLoader)
|
|
||||||
mFragment.mUidDataCallbacks.onCreateLoader(0 /* id */, Bundle.EMPTY /* args */);
|
|
||||||
|
|
||||||
final ArrayList<Long> cycles = loader.getCycles();
|
|
||||||
assertThat(cycles).hasSize(2);
|
|
||||||
assertThat(cycles.get(0)).isEqualTo(endTime);
|
|
||||||
assertThat(cycles.get(1)).isEqualTo(startTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onLoadFinished_hasSelectedCycleSpecified_shouldSelectSpecifiedCycle() {
|
|
||||||
final long now = System.currentTimeMillis();
|
|
||||||
final long tenDaysAgo = now - (DateUtils.DAY_IN_MILLIS * 10);
|
|
||||||
final long twentyDaysAgo = now - (DateUtils.DAY_IN_MILLIS * 20);
|
|
||||||
final long thirtyDaysAgo = now - (DateUtils.DAY_IN_MILLIS * 30);
|
|
||||||
final List<NetworkCycleDataForUid> data = new ArrayList<>();
|
|
||||||
NetworkCycleDataForUid.Builder builder = new NetworkCycleDataForUid.Builder();
|
|
||||||
builder.setStartTime(thirtyDaysAgo).setEndTime(twentyDaysAgo).setTotalUsage(9876L);
|
|
||||||
data.add(builder.build());
|
|
||||||
builder = new NetworkCycleDataForUid.Builder();
|
|
||||||
builder.setStartTime(twentyDaysAgo).setEndTime(tenDaysAgo).setTotalUsage(5678L);
|
|
||||||
data.add(builder.build());
|
|
||||||
builder = new NetworkCycleDataForUid.Builder();
|
|
||||||
builder.setStartTime(tenDaysAgo).setEndTime(now).setTotalUsage(1234L);
|
|
||||||
data.add(builder.build());
|
|
||||||
|
|
||||||
mFragment = new TestFragment();
|
|
||||||
ReflectionHelpers.setField(mFragment, "mContext", RuntimeEnvironment.application);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mCycleAdapter", mock(CycleAdapter.class));
|
|
||||||
ReflectionHelpers.setField(mFragment, "mSelectedCycle", tenDaysAgo);
|
|
||||||
final Preference backgroundPref = mock(Preference.class);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mBackgroundUsage", backgroundPref);
|
|
||||||
final Preference foregroundPref = mock(Preference.class);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mForegroundUsage", foregroundPref);
|
|
||||||
final Preference totalPref = mock(Preference.class);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mTotalUsage", totalPref);
|
|
||||||
final SpinnerPreference cycle = mock(SpinnerPreference.class);
|
|
||||||
ReflectionHelpers.setField(mFragment, "mCycle", cycle);
|
|
||||||
|
|
||||||
mFragment.mUidDataCallbacks.onLoadFinished(null /* loader */, data);
|
|
||||||
|
|
||||||
verify(cycle).setSelection(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(shadows = {ShadowDataUsageUtils.class, ShadowSubscriptionManager.class,
|
@Config(shadows = {ShadowDataUsageUtils.class, ShadowSubscriptionManager.class,
|
||||||
ShadowFragment.class})
|
ShadowFragment.class})
|
||||||
@@ -447,6 +315,10 @@ public class AppDataUsageTest {
|
|||||||
return mock(clazz);
|
return mock(clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void initCycle(List<Integer> uidList) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSimHardwareVisible(Context context) {
|
public boolean isSimHardwareVisible(Context context) {
|
||||||
return true;
|
return true;
|
||||||
|
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.datausage
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.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.datausage.lib.AppDataUsageDetailsRepository
|
||||||
|
import com.android.settings.datausage.lib.IAppDataUsageDetailsRepository
|
||||||
|
import com.android.settings.datausage.lib.NetworkUsageDetailsData
|
||||||
|
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.doReturn
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.spy
|
||||||
|
import org.mockito.kotlin.stub
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class AppDataUsageCycleControllerTest {
|
||||||
|
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||||
|
|
||||||
|
private val controller = AppDataUsageCycleController(context, KEY)
|
||||||
|
|
||||||
|
private val preference = spy(SpinnerPreference(context, null).apply { key = KEY })
|
||||||
|
|
||||||
|
private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
|
||||||
|
|
||||||
|
private val onUsageDataUpdated: (NetworkUsageDetailsData) -> Unit = {}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
preferenceScreen.addPreference(preference)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onViewCreated_noUsage_hidePreference(): Unit = runBlocking {
|
||||||
|
val repository = object : IAppDataUsageDetailsRepository {
|
||||||
|
override suspend fun queryDetailsForCycles() = emptyList<NetworkUsageDetailsData>()
|
||||||
|
}
|
||||||
|
controller.init(repository, onUsageDataUpdated)
|
||||||
|
controller.displayPreference(preferenceScreen)
|
||||||
|
|
||||||
|
controller.onViewCreated(TestLifecycleOwner())
|
||||||
|
delay(100)
|
||||||
|
|
||||||
|
assertThat(preference.isVisible).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onViewCreated_hasUsage_showPreference(): Unit = runBlocking {
|
||||||
|
val detailsData = NetworkUsageDetailsData(
|
||||||
|
range = Range(1, 2),
|
||||||
|
totalUsage = 11,
|
||||||
|
foregroundUsage = 1,
|
||||||
|
backgroundUsage = 10,
|
||||||
|
)
|
||||||
|
val repository = object : IAppDataUsageDetailsRepository {
|
||||||
|
override suspend fun queryDetailsForCycles() = listOf(detailsData)
|
||||||
|
}
|
||||||
|
controller.init(repository, onUsageDataUpdated)
|
||||||
|
controller.displayPreference(preferenceScreen)
|
||||||
|
|
||||||
|
controller.onViewCreated(TestLifecycleOwner())
|
||||||
|
delay(100)
|
||||||
|
|
||||||
|
assertThat(preference.isVisible).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun setInitialCycles() {
|
||||||
|
val repository = object : IAppDataUsageDetailsRepository {
|
||||||
|
override suspend fun queryDetailsForCycles() = emptyList<NetworkUsageDetailsData>()
|
||||||
|
}
|
||||||
|
controller.init(repository, onUsageDataUpdated)
|
||||||
|
controller.setInitialCycles(
|
||||||
|
initialCycles = listOf(CYCLE2_END_TIME, CYCLE1_END_TIME, CYCLE1_START_TIME),
|
||||||
|
initialSelectedEndTime = CYCLE1_END_TIME,
|
||||||
|
)
|
||||||
|
|
||||||
|
controller.displayPreference(preferenceScreen)
|
||||||
|
|
||||||
|
verify(preference).setSelection(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val KEY = "test_key"
|
||||||
|
const val CYCLE1_START_TIME = 1694444444000L
|
||||||
|
const val CYCLE1_END_TIME = 1695555555000L
|
||||||
|
const val CYCLE2_END_TIME = 1695566666000L
|
||||||
|
}
|
||||||
|
}
|
@@ -17,7 +17,6 @@
|
|||||||
package com.android.settings.datausage
|
package com.android.settings.datausage
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.SparseBooleanArray
|
|
||||||
import androidx.lifecycle.testing.TestLifecycleOwner
|
import androidx.lifecycle.testing.TestLifecycleOwner
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceCategory
|
import androidx.preference.PreferenceCategory
|
||||||
@@ -63,9 +62,7 @@ class AppDataUsageListControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun onViewCreated_singleUid_hidePreference(): Unit = runBlocking {
|
fun onViewCreated_singleUid_hidePreference(): Unit = runBlocking {
|
||||||
controller.init(SparseBooleanArray().apply {
|
controller.init(listOf(UID_0))
|
||||||
put(UID_0, true)
|
|
||||||
})
|
|
||||||
controller.displayPreference(preferenceScreen)
|
controller.displayPreference(preferenceScreen)
|
||||||
|
|
||||||
controller.onViewCreated(TestLifecycleOwner())
|
controller.onViewCreated(TestLifecycleOwner())
|
||||||
@@ -76,10 +73,7 @@ class AppDataUsageListControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun onViewCreated_twoUid_showPreference(): Unit = runBlocking {
|
fun onViewCreated_twoUid_showPreference(): Unit = runBlocking {
|
||||||
controller.init(SparseBooleanArray().apply {
|
controller.init(listOf(UID_0, UID_1))
|
||||||
put(UID_0, true)
|
|
||||||
put(UID_1, true)
|
|
||||||
})
|
|
||||||
controller.displayPreference(preferenceScreen)
|
controller.displayPreference(preferenceScreen)
|
||||||
|
|
||||||
controller.onViewCreated(TestLifecycleOwner())
|
controller.onViewCreated(TestLifecycleOwner())
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
package com.android.settings.datausage
|
package com.android.settings.datausage
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.Range
|
||||||
import androidx.lifecycle.testing.TestLifecycleOwner
|
import androidx.lifecycle.testing.TestLifecycleOwner
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
@@ -39,7 +40,7 @@ class ChartDataUsagePreferenceControllerTest {
|
|||||||
|
|
||||||
private val repository = object : INetworkCycleDataRepository {
|
private val repository = object : INetworkCycleDataRepository {
|
||||||
override suspend fun loadCycles() = emptyList<NetworkUsageData>()
|
override suspend fun loadCycles() = emptyList<NetworkUsageData>()
|
||||||
|
override fun getCycles() = emptyList<Range<Long>>()
|
||||||
override fun getPolicy() = null
|
override fun getPolicy() = null
|
||||||
|
|
||||||
override suspend fun querySummary(startTime: Long, endTime: Long) = when {
|
override suspend fun querySummary(startTime: Long, endTime: Long) = when {
|
||||||
|
@@ -18,6 +18,7 @@ package com.android.settings.datausage
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.NetworkTemplate
|
import android.net.NetworkTemplate
|
||||||
|
import android.util.Range
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Spinner
|
import android.widget.Spinner
|
||||||
@@ -48,9 +49,8 @@ class DataUsageListHeaderControllerTest {
|
|||||||
|
|
||||||
private val repository = object : INetworkCycleDataRepository {
|
private val repository = object : INetworkCycleDataRepository {
|
||||||
override suspend fun loadCycles() = emptyList<NetworkUsageData>()
|
override suspend fun loadCycles() = emptyList<NetworkUsageData>()
|
||||||
|
override fun getCycles() = emptyList<Range<Long>>()
|
||||||
override fun getPolicy() = null
|
override fun getPolicy() = null
|
||||||
|
|
||||||
override suspend fun querySummary(startTime: Long, endTime: Long) = null
|
override suspend fun querySummary(startTime: Long, endTime: Long) = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.datausage.lib
|
||||||
|
|
||||||
|
import android.app.usage.NetworkStats.Bucket
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.NetworkTemplate
|
||||||
|
import android.util.Range
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.spy
|
||||||
|
import org.mockito.kotlin.whenever
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class AppDataUsageDetailsRepositoryTest {
|
||||||
|
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||||
|
|
||||||
|
private val template = mock<NetworkTemplate>()
|
||||||
|
|
||||||
|
private val networkCycleDataRepository = mock<INetworkCycleDataRepository> {
|
||||||
|
on { getCycles() } doReturn listOf(Range(CYCLE1_END_TIME, CYCLE2_END_TIME))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun queryDetailsForCycles_hasCycles(): Unit = runBlocking {
|
||||||
|
val range = Range(CYCLE1_START_TIME, CYCLE1_END_TIME)
|
||||||
|
val repository = spy(
|
||||||
|
AppDataUsageDetailsRepository(
|
||||||
|
context = context,
|
||||||
|
cycles = listOf(CYCLE1_END_TIME, CYCLE1_START_TIME),
|
||||||
|
template = template,
|
||||||
|
uids = listOf(UID),
|
||||||
|
networkCycleDataRepository = networkCycleDataRepository,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
doReturn(ALL_USAGE).whenever(mock).getUsage(range, UID, Bucket.STATE_ALL)
|
||||||
|
doReturn(FOREGROUND_USAGE).whenever(mock).getUsage(range, UID, Bucket.STATE_FOREGROUND)
|
||||||
|
}
|
||||||
|
|
||||||
|
val detailsForCycles = repository.queryDetailsForCycles()
|
||||||
|
|
||||||
|
assertThat(detailsForCycles).containsExactly(
|
||||||
|
NetworkUsageDetailsData(
|
||||||
|
range = range,
|
||||||
|
totalUsage = ALL_USAGE,
|
||||||
|
foregroundUsage = FOREGROUND_USAGE,
|
||||||
|
backgroundUsage = ALL_USAGE - FOREGROUND_USAGE,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun queryDetailsForCycles_defaultCycles(): Unit = runBlocking {
|
||||||
|
val range = Range(CYCLE1_END_TIME, CYCLE2_END_TIME)
|
||||||
|
val repository = spy(
|
||||||
|
AppDataUsageDetailsRepository(
|
||||||
|
context = context,
|
||||||
|
cycles = null,
|
||||||
|
template = template,
|
||||||
|
uids = listOf(UID),
|
||||||
|
networkCycleDataRepository = networkCycleDataRepository,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
doReturn(ALL_USAGE).whenever(mock).getUsage(range, UID, Bucket.STATE_ALL)
|
||||||
|
doReturn(FOREGROUND_USAGE).whenever(mock).getUsage(range, UID, Bucket.STATE_FOREGROUND)
|
||||||
|
}
|
||||||
|
|
||||||
|
val detailsForCycles = repository.queryDetailsForCycles()
|
||||||
|
|
||||||
|
assertThat(detailsForCycles).containsExactly(
|
||||||
|
NetworkUsageDetailsData(
|
||||||
|
range = range,
|
||||||
|
totalUsage = ALL_USAGE,
|
||||||
|
foregroundUsage = FOREGROUND_USAGE,
|
||||||
|
backgroundUsage = ALL_USAGE - FOREGROUND_USAGE,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val CYCLE1_START_TIME = 1694444444000L
|
||||||
|
const val CYCLE1_END_TIME = 1695555555000L
|
||||||
|
const val CYCLE2_END_TIME = 1695566666000L
|
||||||
|
const val UID = 10000
|
||||||
|
|
||||||
|
const val ALL_USAGE = 10L
|
||||||
|
const val FOREGROUND_USAGE = 2L
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user