From 5ed30f4672203f67d5453806aff7c2be0df6afd7 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Fri, 22 Sep 2023 17:02:12 +0800 Subject: [PATCH] New DataUsageListHeaderController Move spinner and config button to DataUsageListHeaderController. Bug: 290856342 Test: manual - on DataUsageList and AppDataUsage Test: unit test Change-Id: I655bd3441b2305708c0052f1203538bb07eef2af --- .../settings/datausage/AppDataUsage.java | 5 +- .../datausage/ChartDataUsagePreference.java | 8 - .../settings/datausage/CycleAdapter.java | 9 +- .../settings/datausage/DataUsageList.java | 137 +++++------------- .../DataUsageListHeaderController.kt | 103 +++++++++++++ .../settings/datausage/SpinnerPreference.java | 1 - .../settings/datausage/DataUsageListTest.java | 84 +++++------ .../DataUsageListHeaderControllerTest.kt | 82 +++++++++++ 8 files changed, 265 insertions(+), 164 deletions(-) create mode 100644 src/com/android/settings/datausage/DataUsageListHeaderController.kt create mode 100644 tests/spa_unit/src/com/android/settings/datausage/DataUsageListHeaderControllerTest.kt diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java index 16455867086..c3be0aa646e 100644 --- a/src/com/android/settings/datausage/AppDataUsage.java +++ b/src/com/android/settings/datausage/AppDataUsage.java @@ -310,7 +310,7 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC private void initCycle() { mCycle = findPreference(KEY_CYCLE); - mCycleAdapter = new CycleAdapter(mContext, mCycle, mCycleListener); + mCycleAdapter = new CycleAdapter(mContext, mCycle); if (mCycles != null) { // If coming from a page like DataUsageList where already has a selected cycle, display // that before loading to reduce flicker. @@ -435,7 +435,7 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC return SettingsEnums.APP_DATA_USAGE; } - private AdapterView.OnItemSelectedListener mCycleListener = + private final AdapterView.OnItemSelectedListener mCycleListener = new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { @@ -471,6 +471,7 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC public void onLoadFinished(@NonNull Loader> loader, List data) { mUsageData = data; + mCycle.setOnItemSelectedListener(mCycleListener); mCycleAdapter.updateCycleList(data); if (mSelectedCycle > 0L) { final int numCycles = data.size(); diff --git a/src/com/android/settings/datausage/ChartDataUsagePreference.java b/src/com/android/settings/datausage/ChartDataUsagePreference.java index e2a103ed2dd..fa467d2a8cc 100644 --- a/src/com/android/settings/datausage/ChartDataUsagePreference.java +++ b/src/com/android/settings/datausage/ChartDataUsagePreference.java @@ -294,14 +294,6 @@ public class ChartDataUsagePreference extends Preference { notifyChanged(); } - public long getInspectStart() { - return mStart; - } - - public long getInspectEnd() { - return mEnd; - } - public void setNetworkCycleData(NetworkCycleChartData data) { mNetworkCycleChartData = data; mStart = data.getStartTime(); diff --git a/src/com/android/settings/datausage/CycleAdapter.java b/src/com/android/settings/datausage/CycleAdapter.java index 2af40120864..90a2035c476 100644 --- a/src/com/android/settings/datausage/CycleAdapter.java +++ b/src/com/android/settings/datausage/CycleAdapter.java @@ -14,7 +14,6 @@ package com.android.settings.datausage; import android.content.Context; -import android.widget.AdapterView; import com.android.settings.Utils; import com.android.settingslib.net.NetworkCycleData; @@ -25,13 +24,10 @@ import java.util.List; public class CycleAdapter extends SettingsSpinnerAdapter { private final SpinnerInterface mSpinner; - private final AdapterView.OnItemSelectedListener mListener; - public CycleAdapter(Context context, SpinnerInterface spinner, - AdapterView.OnItemSelectedListener listener) { + public CycleAdapter(Context context, SpinnerInterface spinner) { super(context); mSpinner = spinner; - mListener = listener; mSpinner.setAdapter(this); } @@ -67,7 +63,6 @@ public class CycleAdapter extends SettingsSpinnerAdapter * updating the inspection range on chartData. */ public void updateCycleList(List cycleData) { - mSpinner.setOnItemSelectedListener(mListener); // stash away currently selected cycle to try restoring below final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem) mSpinner.getSelectedItem(); @@ -122,8 +117,6 @@ public class CycleAdapter extends SettingsSpinnerAdapter public interface SpinnerInterface { void setAdapter(CycleAdapter cycleAdapter); - void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener); - Object getSelectedItem(); void setSelection(int position); diff --git a/src/com/android/settings/datausage/DataUsageList.java b/src/com/android/settings/datausage/DataUsageList.java index 15a56039ed9..f48aacd3953 100644 --- a/src/com/android/settings/datausage/DataUsageList.java +++ b/src/com/android/settings/datausage/DataUsageList.java @@ -28,11 +28,6 @@ import android.telephony.SubscriptionManager; import android.util.EventLog; import android.util.Log; import android.view.View; -import android.view.View.AccessibilityDelegate; -import android.view.accessibility.AccessibilityEvent; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.Spinner; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -43,8 +38,6 @@ import androidx.loader.content.Loader; import androidx.preference.Preference; import com.android.settings.R; -import com.android.settings.core.SubSettingLauncher; -import com.android.settings.datausage.CycleAdapter.SpinnerInterface; import com.android.settings.datausage.lib.BillingCycleRepository; import com.android.settings.network.MobileDataEnabledListener; import com.android.settings.network.MobileNetworkRepository; @@ -58,6 +51,8 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import kotlin.Unit; + /** * Panel showing data usage history across various networks, including options * to inspect based on usage cycle and control through {@link NetworkPolicy}. @@ -90,8 +85,6 @@ public class DataUsageList extends DataUsageBaseFragment @VisibleForTesting int mNetworkType; @VisibleForTesting - Spinner mCycleSpinner; - @VisibleForTesting LoadingViewController mLoadingViewController; private ChartDataUsagePreference mChart; @@ -102,13 +95,15 @@ public class DataUsageList extends DataUsageBaseFragment // Spinner will keep the selected cycle even after paused, this only keeps the displayed cycle, // which need be cleared when resumed. private CycleAdapter.CycleItem mLastDisplayedCycle; - private CycleAdapter mCycleAdapter; private Preference mUsageAmount; - private View mHeader; private MobileNetworkRepository mMobileNetworkRepository; private SubscriptionInfoEntity mSubscriptionInfoEntity; private DataUsageListAppsController mDataUsageListAppsController; private BillingCycleRepository mBillingCycleRepository; + @VisibleForTesting + DataUsageListHeaderController mDataUsageListHeaderController; + + private boolean mIsBillingCycleModifiable = false; @Override public int getMetricsCategory() { @@ -158,50 +153,15 @@ public class DataUsageList extends DataUsageBaseFragment public void onViewCreated(@NonNull View v, Bundle savedInstanceState) { super.onViewCreated(v, savedInstanceState); - mHeader = setPinnedHeaderView(R.layout.apps_filter_spinner); - mHeader.findViewById(R.id.filter_settings).setOnClickListener(btn -> { - final Bundle args = new Bundle(); - args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate); - new SubSettingLauncher(getContext()) - .setDestination(BillingCycleSettings.class.getName()) - .setTitleRes(R.string.billing_cycle) - .setSourceMetricsCategory(getMetricsCategory()) - .setArguments(args) - .launch(); - }); - mCycleSpinner = mHeader.findViewById(R.id.filter_spinner); - mCycleSpinner.setVisibility(View.GONE); - mCycleAdapter = new CycleAdapter(mCycleSpinner.getContext(), new SpinnerInterface() { - @Override - public void setAdapter(CycleAdapter cycleAdapter) { - mCycleSpinner.setAdapter(cycleAdapter); - } - - @Override - public void setOnItemSelectedListener(OnItemSelectedListener listener) { - mCycleSpinner.setOnItemSelectedListener(listener); - } - - @Override - public Object getSelectedItem() { - return mCycleSpinner.getSelectedItem(); - } - - @Override - public void setSelection(int position) { - mCycleSpinner.setSelection(position); - } - }, mCycleListener); - mCycleSpinner.setAccessibilityDelegate(new AccessibilityDelegate() { - @Override - public void sendAccessibilityEvent(View host, int eventType) { - if (eventType == AccessibilityEvent.TYPE_VIEW_SELECTED) { - // Ignore TYPE_VIEW_SELECTED or TalkBack will speak for it at onResume. - return; + mDataUsageListHeaderController = new DataUsageListHeaderController( + setPinnedHeaderView(R.layout.apps_filter_spinner), + mTemplate, + getMetricsCategory(), + (cycle, position) -> { + updateSelectedCycle(cycle, position); + return Unit.INSTANCE; } - super.sendAccessibilityEvent(host, eventType); - } - }); + ); mLoadingViewController = new LoadingViewController( getView().findViewById(R.id.loading_container), getListView()); @@ -213,6 +173,7 @@ public class DataUsageList extends DataUsageBaseFragment mLoadingViewController.showLoadingViewDelayed(); mDataStateListener.start(mSubId); mLastDisplayedCycle = null; + updatePolicy(); // kick off loader for network history // TODO: consider chaining two loaders together instead of reloading @@ -291,36 +252,31 @@ public class DataUsageList extends DataUsageBaseFragment */ @VisibleForTesting void updatePolicy() { - final NetworkPolicy policy = services.mPolicyEditor.getPolicy(mTemplate); - final View configureButton = mHeader.findViewById(R.id.filter_settings); - //SUB SELECT - if (policy != null && isMobileDataAvailable()) { - mChart.setNetworkPolicy(policy); - configureButton.setVisibility(View.VISIBLE); + mIsBillingCycleModifiable = isBillingCycleModifiable(); + if (mIsBillingCycleModifiable) { + mChart.setNetworkPolicy(services.mPolicyEditor.getPolicy(mTemplate)); } else { - // controls are disabled; don't bind warning/limit sweeps - mChart.setNetworkPolicy(null); - configureButton.setVisibility(View.GONE); + mChart.setNetworkPolicy(null); // don't bind warning / limit sweeps } - - // generate cycle list based on policy and available history - if (mCycleData != null) { - mCycleAdapter.updateCycleList(mCycleData); - } - mDataUsageListAppsController.setCycleData(mCycleData); - updateSelectedCycle(); + updateConfigButtonVisibility(); } - private boolean isMobileDataAvailable() { + @VisibleForTesting + boolean isBillingCycleModifiable() { return mBillingCycleRepository.isModifiable(mSubId) && SubscriptionManager.from(requireContext()) .getActiveSubscriptionInfo(mSubId) != null; } + private void updateConfigButtonVisibility() { + mDataUsageListHeaderController.setConfigButtonVisible( + mIsBillingCycleModifiable && mCycleData != null); + } + /** * Updates the chart and detail data when initial loaded or selected cycle changed. */ - private void updateSelectedCycle() { + private void updateSelectedCycle(CycleAdapter.CycleItem cycle, int position) { // Avoid from updating UI after #onStop. if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { return; @@ -332,11 +288,6 @@ public class DataUsageList extends DataUsageBaseFragment return; } - final int position = mCycleSpinner.getSelectedItemPosition(); - if (mCycleAdapter.getCount() == 0 || position < 0) { - return; - } - final CycleAdapter.CycleItem cycle = mCycleAdapter.getItem(position); if (Objects.equals(cycle, mLastDisplayedCycle)) { // Avoid duplicate update to avoid page flash. return; @@ -350,9 +301,10 @@ public class DataUsageList extends DataUsageBaseFragment // update chart to show selected cycle, and update detail data // to match updated sweep bounds. - mChart.setNetworkCycleData(mCycleData.get(position)); + NetworkCycleChartData cycleChartData = mCycleData.get(position); + mChart.setNetworkCycleData(cycleChartData); - updateDetailData(); + updateDetailData(cycleChartData); } /** @@ -360,34 +312,21 @@ public class DataUsageList extends DataUsageBaseFragment * current mode. Updates {@link #mAdapter} with sorted list * of applications data usage. */ - private void updateDetailData() { + private void updateDetailData(NetworkCycleChartData cycleChartData) { if (LOGD) Log.d(TAG, "updateDetailData()"); // kick off loader for detailed stats mDataUsageListAppsController.update( mSubscriptionInfoEntity == null ? null : mSubscriptionInfoEntity.carrierId, - mChart.getInspectStart(), - mChart.getInspectEnd() + cycleChartData.getStartTime(), + cycleChartData.getEndTime() ); - final long totalBytes = mCycleData != null && !mCycleData.isEmpty() - ? mCycleData.get(mCycleSpinner.getSelectedItemPosition()).getTotalUsage() : 0; + final long totalBytes = cycleChartData.getTotalUsage(); final CharSequence totalPhrase = DataUsageUtils.formatDataUsage(getActivity(), totalBytes); mUsageAmount.setTitle(getString(R.string.data_used_template, totalPhrase)); } - private final OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - updateSelectedCycle(); - } - - @Override - public void onNothingSelected(AdapterView parent) { - // ignored - } - }; - @VisibleForTesting final LoaderCallbacks> mNetworkCycleDataCallbacks = new LoaderCallbacks<>() { @@ -404,9 +343,9 @@ public class DataUsageList extends DataUsageBaseFragment List data) { mLoadingViewController.showContent(false /* animate */); mCycleData = data; - // calculate policy cycles based on available data - updatePolicy(); - mCycleSpinner.setVisibility(View.VISIBLE); + mDataUsageListHeaderController.updateCycleData(mCycleData); + updateConfigButtonVisibility(); + mDataUsageListAppsController.setCycleData(mCycleData); } @Override diff --git a/src/com/android/settings/datausage/DataUsageListHeaderController.kt b/src/com/android/settings/datausage/DataUsageListHeaderController.kt new file mode 100644 index 00000000000..e295a4cff3b --- /dev/null +++ b/src/com/android/settings/datausage/DataUsageListHeaderController.kt @@ -0,0 +1,103 @@ +/* + * 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.net.NetworkTemplate +import android.os.Bundle +import android.view.View +import android.view.accessibility.AccessibilityEvent +import android.widget.AdapterView +import android.widget.Spinner +import androidx.annotation.OpenForTesting +import com.android.settings.R +import com.android.settings.core.SubSettingLauncher +import com.android.settings.datausage.CycleAdapter.CycleItem +import com.android.settings.datausage.CycleAdapter.SpinnerInterface +import com.android.settingslib.net.NetworkCycleChartData + +@OpenForTesting +open class DataUsageListHeaderController( + header: View, + template: NetworkTemplate, + sourceMetricsCategory: Int, + private val onItemSelected: (cycleItem: CycleItem, position: Int) -> Unit, +) { + private val context = header.context + private val configureButton: View = header.requireViewById(R.id.filter_settings) + private val cycleSpinner: Spinner = header.requireViewById(R.id.filter_spinner) + private val cycleAdapter = CycleAdapter(context, object : SpinnerInterface { + override fun setAdapter(cycleAdapter: CycleAdapter) { + cycleSpinner.adapter = cycleAdapter + } + + override fun getSelectedItem() = cycleSpinner.selectedItem + + override fun setSelection(position: Int) { + cycleSpinner.setSelection(position) + } + }) + + private val cycleListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + if (0 <= position && position < cycleAdapter.count) { + cycleAdapter.getItem(position)?.let { cycleItem -> + onItemSelected(cycleItem, position) + } + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // ignored + } + } + + init { + configureButton.setOnClickListener { + val args = Bundle().apply { + putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, template) + } + SubSettingLauncher(context).apply { + setDestination(BillingCycleSettings::class.java.name) + setTitleRes(R.string.billing_cycle) + setSourceMetricsCategory(sourceMetricsCategory) + setArguments(args) + }.launch() + } + cycleSpinner.visibility = View.GONE + cycleSpinner.accessibilityDelegate = object : View.AccessibilityDelegate() { + override fun sendAccessibilityEvent(host: View, eventType: Int) { + if (eventType == AccessibilityEvent.TYPE_VIEW_SELECTED) { + // Ignore TYPE_VIEW_SELECTED or TalkBack will speak for it at onResume. + return + } + super.sendAccessibilityEvent(host, eventType) + } + } + } + + open fun setConfigButtonVisible(visible: Boolean) { + configureButton.visibility = if (visible) View.VISIBLE else View.GONE + } + + open fun updateCycleData(cycleData: List) { + cycleSpinner.onItemSelectedListener = cycleListener + // calculate policy cycles based on available data + // generate cycle list based on policy and available history + cycleAdapter.updateCycleList(cycleData) + cycleSpinner.visibility = View.VISIBLE + } +} diff --git a/src/com/android/settings/datausage/SpinnerPreference.java b/src/com/android/settings/datausage/SpinnerPreference.java index c6b5f9f8ecc..a705079b76b 100644 --- a/src/com/android/settings/datausage/SpinnerPreference.java +++ b/src/com/android/settings/datausage/SpinnerPreference.java @@ -47,7 +47,6 @@ public class SpinnerPreference extends Preference implements CycleAdapter.Spinne notifyChanged(); } - @Override public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) { mListener = listener; } diff --git a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java index 5eee615a408..cb2b2789c13 100644 --- a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java +++ b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java @@ -34,17 +34,12 @@ import android.net.NetworkTemplate; import android.os.Bundle; import android.os.UserManager; import android.provider.Settings; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.Spinner; import androidx.annotation.NonNull; -import androidx.fragment.app.FragmentActivity; import androidx.loader.app.LoaderManager; +import androidx.preference.Preference; import androidx.preference.PreferenceManager; -import com.android.settings.R; import com.android.settings.datausage.lib.BillingCycleRepository; import com.android.settings.network.MobileDataEnabledListener; import com.android.settings.testutils.FakeFeatureFactory; @@ -52,6 +47,7 @@ import com.android.settings.widget.LoadingViewController; import com.android.settingslib.NetworkPolicyEditor; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; +import com.android.settingslib.net.NetworkCycleChartData; import org.junit.Before; import org.junit.Rule; @@ -69,7 +65,11 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.util.ReflectionHelpers; +import java.util.Collections; +import java.util.List; + @RunWith(RobolectricTestRunner.class) +@Config(shadows = DataUsageListTest.ShadowDataUsageBaseFragment.class) public class DataUsageListTest { @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); @@ -84,6 +84,8 @@ public class DataUsageListTest { private UserManager mUserManager; @Mock private BillingCycleRepository mBillingCycleRepository; + @Mock + private DataUsageListHeaderController mDataUsageListHeaderController; private Activity mActivity; @@ -98,7 +100,6 @@ public class DataUsageListTest { mActivity = spy(mActivityController.get()); mNetworkServices.mPolicyEditor = mock(NetworkPolicyEditor.class); mDataUsageList.mDataStateListener = mMobileDataEnabledListener; - mDataUsageList.mTemplate = mock(NetworkTemplate.class); doReturn(mActivity).when(mDataUsageList).getContext(); doReturn(mUserManager).when(mActivity).getSystemService(UserManager.class); @@ -110,11 +111,12 @@ public class DataUsageListTest { mDataUsageList.mLoadingViewController = mock(LoadingViewController.class); doNothing().when(mDataUsageList).updateSubscriptionInfoEntity(); when(mBillingCycleRepository.isBandwidthControlEnabled()).thenReturn(true); + mDataUsageList.mDataUsageListHeaderController = mDataUsageListHeaderController; } @Test - @Config(shadows = ShadowDataUsageBaseFragment.class) public void onCreate_isNotGuestUser_shouldNotFinish() { + mDataUsageList.mTemplate = mock(NetworkTemplate.class); doReturn(false).when(mUserManager).isGuestUser(); doNothing().when(mDataUsageList).processArgument(); @@ -124,7 +126,6 @@ public class DataUsageListTest { } @Test - @Config(shadows = ShadowDataUsageBaseFragment.class) public void onCreate_isGuestUser_shouldFinish() { doReturn(true).when(mUserManager).isGuestUser(); @@ -135,6 +136,7 @@ public class DataUsageListTest { @Test public void resume_shouldListenDataStateChange() { + mDataUsageList.onCreate(null); ReflectionHelpers.setField( mDataUsageList, "mVisibilityLoggerMixin", mock(VisibilityLoggerMixin.class)); ReflectionHelpers.setField( @@ -149,6 +151,7 @@ public class DataUsageListTest { @Test public void pause_shouldUnlistenDataStateChange() { + mDataUsageList.onCreate(null); ReflectionHelpers.setField( mDataUsageList, "mVisibilityLoggerMixin", mock(VisibilityLoggerMixin.class)); ReflectionHelpers.setField( @@ -187,12 +190,10 @@ public class DataUsageListTest { @Test public void processArgument_fromIntent_shouldGetTemplateFromIntent() { - final FragmentActivity activity = mock(FragmentActivity.class); final Intent intent = new Intent(); intent.putExtra(Settings.EXTRA_NETWORK_TEMPLATE, mock(NetworkTemplate.class)); intent.putExtra(Settings.EXTRA_SUB_ID, 3); - when(activity.getIntent()).thenReturn(intent); - doReturn(activity).when(mDataUsageList).getActivity(); + doReturn(intent).when(mDataUsageList).getIntent(); mDataUsageList.processArgument(); @@ -200,31 +201,17 @@ public class DataUsageListTest { assertThat(mDataUsageList.mSubId).isEqualTo(3); } - @Test - public void onViewCreated_shouldHideCycleSpinner() { - final View view = new View(mActivity); - final View header = getHeader(); - final Spinner spinner = getSpinner(header); - spinner.setVisibility(View.VISIBLE); - doReturn(header).when(mDataUsageList).setPinnedHeaderView(anyInt()); - doReturn(view).when(mDataUsageList).getView(); - - mDataUsageList.onViewCreated(view, null); - - assertThat(spinner.getVisibility()).isEqualTo(View.GONE); - } - @Test public void onLoadFinished_networkCycleDataCallback_shouldShowCycleSpinner() { - final Spinner spinner = getSpinner(getHeader()); - spinner.setVisibility(View.INVISIBLE); - mDataUsageList.mCycleSpinner = spinner; - assertThat(spinner.getVisibility()).isEqualTo(View.INVISIBLE); - doNothing().when(mDataUsageList).updatePolicy(); + mDataUsageList.mTemplate = mock(NetworkTemplate.class); + mDataUsageList.onCreate(null); + mDataUsageList.updatePolicy(); + List mockData = Collections.emptyList(); - mDataUsageList.mNetworkCycleDataCallbacks.onLoadFinished(null, null); + mDataUsageList.mNetworkCycleDataCallbacks.onLoadFinished(null, mockData); - assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE); + verify(mDataUsageListHeaderController).updateCycleData(mockData); + verify(mDataUsageListHeaderController).setConfigButtonVisible(true); } @Test @@ -234,19 +221,6 @@ public class DataUsageListTest { verify(mLoaderManager).destroyLoader(DataUsageList.LOADER_CHART_DATA); } - private View getHeader() { - final View rootView = LayoutInflater.from(mActivity) - .inflate(R.layout.preference_list_fragment, null, false); - final FrameLayout pinnedHeader = rootView.findViewById(R.id.pinned_header); - - return mActivity.getLayoutInflater() - .inflate(R.layout.apps_filter_spinner, pinnedHeader, false); - } - - private Spinner getSpinner(View header) { - return header.findViewById(R.id.filter_spinner); - } - @Implements(DataUsageBaseFragment.class) public static class ShadowDataUsageBaseFragment { @Implementation @@ -261,10 +235,28 @@ public class DataUsageListTest { return mock(clazz); } + @Override + public T findPreference(CharSequence key) { + if (key.toString().equals("chart_data")) { + return (T) mock(ChartDataUsagePreference.class); + } + return (T) mock(Preference.class); + } + + @Override + public Intent getIntent() { + return new Intent(); + } + @NonNull @Override BillingCycleRepository createBillingCycleRepository() { return mBillingCycleRepository; } + + @Override + boolean isBillingCycleModifiable() { + return true; + } } } diff --git a/tests/spa_unit/src/com/android/settings/datausage/DataUsageListHeaderControllerTest.kt b/tests/spa_unit/src/com/android/settings/datausage/DataUsageListHeaderControllerTest.kt new file mode 100644 index 00000000000..a1eebe79ade --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/datausage/DataUsageListHeaderControllerTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.datausage + +import android.content.Context +import android.net.NetworkTemplate +import android.view.LayoutInflater +import android.view.View +import android.widget.Spinner +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.R +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.whenever + +@RunWith(AndroidJUnit4::class) +class DataUsageListHeaderControllerTest { + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + doNothing().whenever(mock).startActivity(any()) + } + + private val header = + LayoutInflater.from(context).inflate(R.layout.apps_filter_spinner, null, false) + + private val configureButton: View = header.requireViewById(R.id.filter_settings) + + private val spinner: Spinner = header.requireViewById(R.id.filter_spinner) + + private val controller = DataUsageListHeaderController( + header = header, + template = mock(), + sourceMetricsCategory = 0, + onItemSelected = { _, _ -> }, + ) + + @Test + fun onViewCreated_shouldHideCycleSpinner() { + assertThat(spinner.visibility).isEqualTo(View.GONE) + } + + @Test + fun updateCycleData_shouldShowCycleSpinner() { + controller.updateCycleData(emptyList()) + + assertThat(spinner.visibility).isEqualTo(View.VISIBLE) + } + + @Test + fun setConfigButtonVisible_setToTrue_shouldShowConfigureButton() { + controller.setConfigButtonVisible(true) + + assertThat(configureButton.visibility).isEqualTo(View.VISIBLE) + } + + @Test + fun setConfigButtonVisible_setToFalse_shouldHideConfigureButton() { + controller.setConfigButtonVisible(false) + + assertThat(configureButton.visibility).isEqualTo(View.GONE) + } +}