diff --git a/src/com/android/settings/core/FeatureFlags.java b/src/com/android/settings/core/FeatureFlags.java index 1062811cfa5..0fe37e91027 100644 --- a/src/com/android/settings/core/FeatureFlags.java +++ b/src/com/android/settings/core/FeatureFlags.java @@ -25,4 +25,5 @@ public class FeatureFlags { public static final String DYNAMIC_HOMEPAGE = "settings_dynamic_homepage"; public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid"; public static final String MOBILE_NETWORK_V2 = "settings_mobile_network_v2"; + public static final String DATA_USAGE_V2 = "settings_data_usage_v2"; } diff --git a/src/com/android/settings/datausage/DataUsageList.java b/src/com/android/settings/datausage/DataUsageList.java index b52d3998510..057cdd7f61d 100644 --- a/src/com/android/settings/datausage/DataUsageList.java +++ b/src/com/android/settings/datausage/DataUsageList.java @@ -75,7 +75,12 @@ import java.util.List; /** * Panel showing data usage history across various networks, including options * to inspect based on usage cycle and control through {@link NetworkPolicy}. + + * Deprecated in favor of {@link DataUsageListV2} + * + * @deprecated */ +@Deprecated public class DataUsageList extends DataUsageBaseFragment { public static final String EXTRA_SUB_ID = "sub_id"; diff --git a/src/com/android/settings/datausage/DataUsageListV2.java b/src/com/android/settings/datausage/DataUsageListV2.java new file mode 100644 index 00000000000..fd20e23a37c --- /dev/null +++ b/src/com/android/settings/datausage/DataUsageListV2.java @@ -0,0 +1,615 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.settings.datausage; + +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; +import static android.net.TrafficStats.UID_REMOVED; +import static android.net.TrafficStats.UID_TETHERING; +import static android.telephony.TelephonyManager.SIM_STATE_READY; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.graphics.Color; +import android.net.ConnectivityManager; +import android.net.INetworkStatsSession; +import android.net.NetworkPolicy; +import android.net.NetworkStats; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.net.TrafficStats; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.format.DateUtils; +import android.util.Log; +import android.util.SparseArray; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ImageView; +import android.widget.Spinner; + +import androidx.annotation.VisibleForTesting; +import androidx.loader.app.LoaderManager.LoaderCallbacks; +import androidx.loader.content.Loader; +import androidx.preference.Preference; +import androidx.preference.PreferenceGroup; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.datausage.CycleAdapter.SpinnerInterface; +import com.android.settings.widget.LoadingViewController; +import com.android.settingslib.AppItem; +import com.android.settingslib.net.ChartData; +import com.android.settingslib.net.ChartDataLoaderCompat; +import com.android.settingslib.net.SummaryForAllUidLoaderCompat; +import com.android.settingslib.net.UidDetailProvider; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Panel showing data usage history across various networks, including options + * to inspect based on usage cycle and control through {@link NetworkPolicy}. + */ +public class DataUsageListV2 extends DataUsageBaseFragment { + + public static final String EXTRA_SUB_ID = "sub_id"; + public static final String EXTRA_NETWORK_TEMPLATE = "network_template"; + + private static final String TAG = "DataUsageListV2"; + private static final boolean LOGD = false; + + private static final String KEY_USAGE_AMOUNT = "usage_amount"; + private static final String KEY_CHART_DATA = "chart_data"; + private static final String KEY_APPS_GROUP = "apps_group"; + + private static final int LOADER_CHART_DATA = 2; + private static final int LOADER_SUMMARY = 3; + + private final CellDataPreference.DataStateListener mDataStateListener = + new CellDataPreference.DataStateListener() { + @Override + public void onChange(boolean selfChange) { + updatePolicy(); + } + }; + + private INetworkStatsSession mStatsSession; + private ChartDataUsagePreference mChart; + + @VisibleForTesting + NetworkTemplate mTemplate; + @VisibleForTesting + int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private ChartData mChartData; + + private LoadingViewController mLoadingViewController; + private UidDetailProvider mUidDetailProvider; + private CycleAdapter mCycleAdapter; + private Spinner mCycleSpinner; + private Preference mUsageAmount; + private PreferenceGroup mApps; + private View mHeader; + + + @Override + public int getMetricsCategory() { + return MetricsEvent.DATA_USAGE_LIST; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Context context = getActivity(); + + if (!isBandwidthControlEnabled()) { + Log.w(TAG, "No bandwidth control; leaving"); + getActivity().finish(); + } + + try { + mStatsSession = services.mStatsService.openSession(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + + mUidDetailProvider = new UidDetailProvider(context); + + mUsageAmount = findPreference(KEY_USAGE_AMOUNT); + mChart = (ChartDataUsagePreference) findPreference(KEY_CHART_DATA); + mApps = (PreferenceGroup) findPreference(KEY_APPS_GROUP); + processArgument(); + } + + @Override + public void onViewCreated(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(DataUsageListV2.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); + 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, true); + + mLoadingViewController = new LoadingViewController( + getView().findViewById(R.id.loading_container), getListView()); + mLoadingViewController.showLoadingViewDelayed(); + } + + @Override + public void onResume() { + super.onResume(); + mDataStateListener.setListener(true, mSubId, getContext()); + updateBody(); + + // kick off background task to update stats + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + try { + // wait a few seconds before kicking off + Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS); + services.mStatsService.forceUpdate(); + } catch (InterruptedException e) { + } catch (RemoteException e) { + } + return null; + } + + @Override + protected void onPostExecute(Void result) { + if (isAdded()) { + updateBody(); + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + @Override + public void onPause() { + super.onPause(); + mDataStateListener.setListener(false, mSubId, getContext()); + } + + @Override + public void onDestroy() { + mUidDetailProvider.clearCache(); + mUidDetailProvider = null; + + TrafficStats.closeQuietly(mStatsSession); + + super.onDestroy(); + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.data_usage_list; + } + + @Override + protected String getLogTag() { + return TAG; + } + + void processArgument() { + final Bundle args = getArguments(); + if (args != null) { + mSubId = args.getInt(EXTRA_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); + mTemplate = args.getParcelable(EXTRA_NETWORK_TEMPLATE); + } + if (mTemplate == null && mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + final Intent intent = getIntent(); + mSubId = intent.getIntExtra(Settings.EXTRA_SUB_ID, + SubscriptionManager.INVALID_SUBSCRIPTION_ID); + mTemplate = intent.getParcelableExtra(Settings.EXTRA_NETWORK_TEMPLATE); + } + } + + /** + * Update body content based on current tab. Loads + * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and + * binds them to visible controls. + */ + private void updateBody() { + if (!isAdded()) return; + + final Context context = getActivity(); + + // kick off loader for network history + // TODO: consider chaining two loaders together instead of reloading + // network history when showing app detail. + getLoaderManager().restartLoader(LOADER_CHART_DATA, + ChartDataLoaderCompat.buildArgs(mTemplate, null), mChartDataCallbacks); + + // detail mode can change visible menus, invalidate + getActivity().invalidateOptionsMenu(); + + int seriesColor = context.getColor(R.color.sim_noitification); + if (mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + final SubscriptionInfo sir = services.mSubscriptionManager + .getActiveSubscriptionInfo(mSubId); + + if (sir != null) { + seriesColor = sir.getIconTint(); + } + } + + final int secondaryColor = Color.argb(127, Color.red(seriesColor), Color.green(seriesColor), + Color.blue(seriesColor)); + mChart.setColors(seriesColor, secondaryColor); + } + + /** + * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for + * current {@link #mTemplate}. + */ + private void updatePolicy() { + final NetworkPolicy policy = services.mPolicyEditor.getPolicy(mTemplate); + final View configureButton = mHeader.findViewById(R.id.filter_settings); + //SUB SELECT + if (isNetworkPolicyModifiable(policy, mSubId) && isMobileDataAvailable(mSubId)) { + mChart.setNetworkPolicy(policy); + configureButton.setVisibility(View.VISIBLE); + ((ImageView) configureButton).setColorFilter(android.R.color.white); + } else { + // controls are disabled; don't bind warning/limit sweeps + mChart.setNetworkPolicy(null); + configureButton.setVisibility(View.GONE); + } + + // generate cycle list based on policy and available history + if (mCycleAdapter.updateCycleList(policy, mChartData)) { + updateDetailData(); + } + } + + /** + * Update details based on {@link #mChart} inspection range depending on + * current mode. Updates {@link #mAdapter} with sorted list + * of applications data usage. + */ + private void updateDetailData() { + if (LOGD) Log.d(TAG, "updateDetailData()"); + + final long start = mChart.getInspectStart(); + final long end = mChart.getInspectEnd(); + final long now = System.currentTimeMillis(); + + final Context context = getActivity(); + + NetworkStatsHistory.Entry entry = null; + if (mChartData != null) { + entry = mChartData.network.getValues(start, end, now, null); + } + + // kick off loader for detailed stats + getLoaderManager().restartLoader(LOADER_SUMMARY, + SummaryForAllUidLoaderCompat.buildArgs(mTemplate, start, end), mSummaryCallbacks); + + final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0; + final CharSequence totalPhrase = DataUsageUtils.formatDataUsage(context, totalBytes); + mUsageAmount.setTitle(getString(R.string.data_used_template, totalPhrase)); + } + + /** + * Bind the given {@link NetworkStats}, or {@code null} to clear list. + */ + public void bindStats(NetworkStats stats, int[] restrictedUids) { + ArrayList items = new ArrayList<>(); + long largest = 0; + + final int currentUserId = ActivityManager.getCurrentUser(); + UserManager userManager = UserManager.get(getContext()); + final List profiles = userManager.getUserProfiles(); + final SparseArray knownItems = new SparseArray(); + + NetworkStats.Entry entry = null; + final int size = stats != null ? stats.size() : 0; + for (int i = 0; i < size; i++) { + entry = stats.getValues(i, entry); + + // Decide how to collapse items together + final int uid = entry.uid; + + final int collapseKey; + final int category; + final int userId = UserHandle.getUserId(uid); + if (UserHandle.isApp(uid)) { + if (profiles.contains(new UserHandle(userId))) { + if (userId != currentUserId) { + // Add to a managed user item. + final int managedKey = UidDetailProvider.buildKeyForUser(userId); + largest = accumulate(managedKey, knownItems, entry, AppItem.CATEGORY_USER, + items, largest); + } + // Add to app item. + collapseKey = uid; + category = AppItem.CATEGORY_APP; + } else { + // If it is a removed user add it to the removed users' key + final UserInfo info = userManager.getUserInfo(userId); + if (info == null) { + collapseKey = UID_REMOVED; + category = AppItem.CATEGORY_APP; + } else { + // Add to other user item. + collapseKey = UidDetailProvider.buildKeyForUser(userId); + category = AppItem.CATEGORY_USER; + } + } + } else if (uid == UID_REMOVED || uid == UID_TETHERING) { + collapseKey = uid; + category = AppItem.CATEGORY_APP; + } else { + collapseKey = android.os.Process.SYSTEM_UID; + category = AppItem.CATEGORY_APP; + } + largest = accumulate(collapseKey, knownItems, entry, category, items, largest); + } + + final int restrictedUidsMax = restrictedUids.length; + for (int i = 0; i < restrictedUidsMax; ++i) { + final int uid = restrictedUids[i]; + // Only splice in restricted state for current user or managed users + if (!profiles.contains(new UserHandle(UserHandle.getUserId(uid)))) { + continue; + } + + AppItem item = knownItems.get(uid); + if (item == null) { + item = new AppItem(uid); + item.total = -1; + items.add(item); + knownItems.put(item.key, item); + } + item.restricted = true; + } + + Collections.sort(items); + mApps.removeAll(); + for (int i = 0; i < items.size(); i++) { + final int percentTotal = largest != 0 ? (int) (items.get(i).total * 100 / largest) : 0; + AppDataUsagePreference preference = new AppDataUsagePreference(getContext(), + items.get(i), percentTotal, mUidDetailProvider); + preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + AppDataUsagePreference pref = (AppDataUsagePreference) preference; + AppItem item = pref.getItem(); + startAppDataUsage(item); + return true; + } + }); + mApps.addPreference(preference); + } + } + + private void startAppDataUsage(AppItem item) { + final Bundle args = new Bundle(); + args.putParcelable(AppDataUsage.ARG_APP_ITEM, item); + args.putParcelable(AppDataUsage.ARG_NETWORK_TEMPLATE, mTemplate); + + new SubSettingLauncher(getContext()) + .setDestination(AppDataUsage.class.getName()) + .setTitleRes(R.string.app_data_usage) + .setArguments(args) + .setSourceMetricsCategory(getMetricsCategory()) + .launch(); + } + + /** + * Accumulate data usage of a network stats entry for the item mapped by the collapse key. + * Creates the item if needed. + * + * @param collapseKey the collapse key used to map the item. + * @param knownItems collection of known (already existing) items. + * @param entry the network stats entry to extract data usage from. + * @param itemCategory the item is categorized on the list view by this category. Must be + */ + private static long accumulate(int collapseKey, final SparseArray knownItems, + NetworkStats.Entry entry, int itemCategory, ArrayList items, long largest) { + final int uid = entry.uid; + AppItem item = knownItems.get(collapseKey); + if (item == null) { + item = new AppItem(collapseKey); + item.category = itemCategory; + items.add(item); + knownItems.put(item.key, item); + } + item.addUid(uid); + item.total += entry.rxBytes + entry.txBytes; + return Math.max(largest, item.total); + } + + /** + * Test if device has a mobile data radio with SIM in ready state. + */ + public static boolean hasReadyMobileRadio(Context context) { + if (DataUsageUtils.TEST_RADIOS) { + return SystemProperties.get(DataUsageUtils.TEST_RADIOS_PROP).contains("mobile"); + } + + final ConnectivityManager conn = ConnectivityManager.from(context); + final TelephonyManager tele = TelephonyManager.from(context); + + final List subInfoList = + SubscriptionManager.from(context).getActiveSubscriptionInfoList(); + // No activated Subscriptions + if (subInfoList == null) { + if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfoList=null"); + return false; + } + // require both supported network and ready SIM + boolean isReady = true; + for (SubscriptionInfo subInfo : subInfoList) { + isReady = isReady & tele.getSimState(subInfo.getSimSlotIndex()) == SIM_STATE_READY; + if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfo=" + subInfo); + } + boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady; + if (LOGD) { + Log.d(TAG, "hasReadyMobileRadio:" + + " conn.isNetworkSupported(TYPE_MOBILE)=" + + conn.isNetworkSupported(TYPE_MOBILE) + + " isReady=" + isReady); + } + return retVal; + } + + /* + * TODO: consider adding to TelephonyManager or SubscriptionManager. + */ + public static boolean hasReadyMobileRadio(Context context, int subId) { + if (DataUsageUtils.TEST_RADIOS) { + return SystemProperties.get(DataUsageUtils.TEST_RADIOS_PROP).contains("mobile"); + } + + final ConnectivityManager conn = ConnectivityManager.from(context); + final TelephonyManager tele = TelephonyManager.from(context); + final int slotId = SubscriptionManager.getSlotIndex(subId); + final boolean isReady = tele.getSimState(slotId) == SIM_STATE_READY; + + boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady; + if (LOGD) { + Log.d(TAG, "hasReadyMobileRadio: subId=" + subId + + " conn.isNetworkSupported(TYPE_MOBILE)=" + + conn.isNetworkSupported(TYPE_MOBILE) + + " isReady=" + isReady); + } + return retVal; + } + + private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + final CycleAdapter.CycleItem cycle = (CycleAdapter.CycleItem) + mCycleSpinner.getSelectedItem(); + + if (LOGD) { + Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end=" + + cycle.end + "]"); + } + + // update chart to show selected cycle, and update detail data + // to match updated sweep bounds. + mChart.setVisibleRange(cycle.start, cycle.end); + + updateDetailData(); + } + + @Override + public void onNothingSelected(AdapterView parent) { + // ignored + } + }; + + private final LoaderCallbacks mChartDataCallbacks = new LoaderCallbacks< + ChartData>() { + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new ChartDataLoaderCompat(getActivity(), mStatsSession, args); + } + + @Override + public void onLoadFinished(Loader loader, ChartData data) { + mLoadingViewController.showContent(false /* animate */); + mChartData = data; + mChart.setNetworkStats(mChartData.network); + + // calculate policy cycles based on available data + updatePolicy(); + } + + @Override + public void onLoaderReset(Loader loader) { + mChartData = null; + mChart.setNetworkStats(null); + } + }; + + private final LoaderCallbacks mSummaryCallbacks = new LoaderCallbacks< + NetworkStats>() { + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new SummaryForAllUidLoaderCompat(getActivity(), mStatsSession, args); + } + + @Override + public void onLoadFinished(Loader loader, NetworkStats data) { + final int[] restrictedUids = services.mPolicyManager.getUidsWithPolicy( + POLICY_REJECT_METERED_BACKGROUND); + bindStats(data, restrictedUids); + updateEmptyVisible(); + } + + @Override + public void onLoaderReset(Loader loader) { + bindStats(null, new int[0]); + updateEmptyVisible(); + } + + private void updateEmptyVisible() { + if ((mApps.getPreferenceCount() != 0) != + (getPreferenceScreen().getPreferenceCount() != 0)) { + if (mApps.getPreferenceCount() != 0) { + getPreferenceScreen().addPreference(mUsageAmount); + getPreferenceScreen().addPreference(mApps); + } else { + getPreferenceScreen().removeAll(); + } + } + } + }; +} diff --git a/src/com/android/settings/datausage/DataUsagePreference.java b/src/com/android/settings/datausage/DataUsagePreference.java index fd5c44ebe82..cdec619a6bb 100644 --- a/src/com/android/settings/datausage/DataUsagePreference.java +++ b/src/com/android/settings/datausage/DataUsagePreference.java @@ -20,6 +20,7 @@ import android.content.res.TypedArray; import android.net.NetworkTemplate; import android.os.Bundle; import android.util.AttributeSet; +import android.util.FeatureFlagUtils; import androidx.annotation.VisibleForTesting; import androidx.core.content.res.TypedArrayUtils; @@ -27,6 +28,7 @@ import androidx.preference.Preference; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; +import com.android.settings.core.FeatureFlags; import com.android.settings.core.SubSettingLauncher; import com.android.settingslib.net.DataUsageController; @@ -75,12 +77,22 @@ public class DataUsagePreference extends Preference implements TemplatePreferenc @Override public Intent getIntent() { final Bundle args = new Bundle(); - args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate); - args.putInt(DataUsageList.EXTRA_SUB_ID, mSubId); - final SubSettingLauncher launcher = new SubSettingLauncher(getContext()) + final SubSettingLauncher launcher; + if (FeatureFlagUtils.isEnabled(getContext(), FeatureFlags.DATA_USAGE_V2)) { + args.putParcelable(DataUsageListV2.EXTRA_NETWORK_TEMPLATE, mTemplate); + args.putInt(DataUsageListV2.EXTRA_SUB_ID, mSubId); + launcher = new SubSettingLauncher(getContext()) + .setArguments(args) + .setDestination(DataUsageListV2.class.getName()) + .setSourceMetricsCategory(MetricsProto.MetricsEvent.VIEW_UNKNOWN); + } else { + args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate); + args.putInt(DataUsageList.EXTRA_SUB_ID, mSubId); + launcher = new SubSettingLauncher(getContext()) .setArguments(args) .setDestination(DataUsageList.class.getName()) .setSourceMetricsCategory(MetricsProto.MetricsEvent.VIEW_UNKNOWN); + } if (mTemplate.isMatchRuleMobile()) { launcher.setTitleRes(R.string.app_cellular_data_usage); } else { diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider index 1a7b28958a5..1ed50c93b67 100644 --- a/tests/robotests/assets/grandfather_not_implementing_index_provider +++ b/tests/robotests/assets/grandfather_not_implementing_index_provider @@ -29,6 +29,7 @@ com.android.settings.bluetooth.BluetoothPairingDetail com.android.settings.bluetooth.DevicePickerFragment com.android.settings.datausage.AppDataUsage com.android.settings.datausage.DataUsageList +com.android.settings.datausage.DataUsageListV2 com.android.settings.datetime.timezone.TimeZoneSettings com.android.settings.deviceinfo.PrivateVolumeSettings com.android.settings.deviceinfo.PublicVolumeSettings diff --git a/tests/robotests/src/com/android/settings/datausage/DataUsageListV2Test.java b/tests/robotests/src/com/android/settings/datausage/DataUsageListV2Test.java new file mode 100644 index 00000000000..9eb800375bd --- /dev/null +++ b/tests/robotests/src/com/android/settings/datausage/DataUsageListV2Test.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.datausage; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.Intent; +import android.net.NetworkTemplate; +import android.os.Bundle; +import android.provider.Settings; + +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.NetworkPolicyEditor; +import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.util.ReflectionHelpers; + +import androidx.fragment.app.FragmentActivity; +import androidx.preference.PreferenceManager; + +@RunWith(SettingsRobolectricTestRunner.class) +public class DataUsageListV2Test { + + @Mock + private CellDataPreference.DataStateListener mListener; + @Mock + private TemplatePreference.NetworkServices mNetworkServices; + @Mock + private Context mContext; + private DataUsageListV2 mDataUsageList; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + FakeFeatureFactory.setupForTest(); + mNetworkServices.mPolicyEditor = mock(NetworkPolicyEditor.class); + mDataUsageList = spy(DataUsageListV2.class); + + doReturn(mContext).when(mDataUsageList).getContext(); + ReflectionHelpers.setField(mDataUsageList, "mDataStateListener", mListener); + ReflectionHelpers.setField(mDataUsageList, "services", mNetworkServices); + } + + @Test + public void resumePause_shouldListenUnlistenDataStateChange() { + ReflectionHelpers.setField( + mDataUsageList, "mVisibilityLoggerMixin", mock(VisibilityLoggerMixin.class)); + ReflectionHelpers.setField( + mDataUsageList, "mPreferenceManager", mock(PreferenceManager.class)); + + mDataUsageList.onResume(); + + verify(mListener).setListener(true, mDataUsageList.mSubId, mContext); + + mDataUsageList.onPause(); + + verify(mListener).setListener(false, mDataUsageList.mSubId, mContext); + } + + @Test + public void processArgument_shouldGetTemplateFromArgument() { + final Bundle args = new Bundle(); + args.putParcelable(DataUsageListV2.EXTRA_NETWORK_TEMPLATE, mock(NetworkTemplate.class)); + args.putInt(DataUsageListV2.EXTRA_SUB_ID, 3); + mDataUsageList.setArguments(args); + + mDataUsageList.processArgument(); + + assertThat(mDataUsageList.mTemplate).isNotNull(); + assertThat(mDataUsageList.mSubId).isEqualTo(3); + } + + @Test + public void processArgument_fromIntent_shouldGetTemplateFromIntent() { + final 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(); + + mDataUsageList.processArgument(); + + assertThat(mDataUsageList.mTemplate).isNotNull(); + assertThat(mDataUsageList.mSubId).isEqualTo(3); + } +}