Merge changes from topic "AppDataUsage" into main

* changes:
  [Expressive design] Update AppDataUsage.
  [Expressive design] Rename ComposeMainSwitchPreference to ComposeGroupSectionPreference.
This commit is contained in:
Yuchen Sun
2024-12-17 00:05:34 -08:00
committed by Android (Google) Code Review
7 changed files with 72 additions and 52 deletions

View File

@@ -20,11 +20,15 @@
android:key="app_data_usage_screen" android:key="app_data_usage_screen"
android:title="@string/data_usage_app_summary_title"> android:title="@string/data_usage_app_summary_title">
<com.android.settingslib.widget.IntroPreference
android:key="app_header"
android:order="-10000"/>
<com.android.settings.datausage.SpinnerPreference <com.android.settings.datausage.SpinnerPreference
android:key="cycle" android:key="cycle"
settings:controller="com.android.settings.datausage.AppDataUsageCycleController" /> settings:controller="com.android.settings.datausage.AppDataUsageCycleController" />
<com.android.settings.spa.preference.ComposePreference <com.android.settings.spa.preference.ComposeGroupSectionPreference
android:key="app_data_usage_summary" android:key="app_data_usage_summary"
settings:controller="com.android.settings.datausage.AppDataUsageSummaryController"/> settings:controller="com.android.settings.datausage.AppDataUsageSummaryController"/>

View File

@@ -18,7 +18,7 @@
xmlns:settings="http://schemas.android.com/apk/res-auto" xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="mobile_network_pref_screen"> android:key="mobile_network_pref_screen">
<com.android.settings.spa.preference.ComposeMainSwitchPreference <com.android.settings.spa.preference.ComposeGroupSectionPreference
android:key="use_sim_switch" android:key="use_sim_switch"
settings:controller="com.android.settings.network.telephony.MobileNetworkSwitchController"/> settings:controller="com.android.settings.network.telephony.MobileNetworkSwitchController"/>

View File

@@ -18,6 +18,7 @@ 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 static com.android.settings.datausage.lib.AppDataUsageRepository.getAppUidList;
import static com.android.settings.spa.app.appinfo.AppInfoSettingsProvider.startAppInfoSettings;
import android.app.Activity; import android.app.Activity;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
@@ -45,13 +46,14 @@ import com.android.settings.datausage.lib.AppDataUsageDetailsRepository;
import com.android.settings.datausage.lib.NetworkTemplates; import com.android.settings.datausage.lib.NetworkTemplates;
import com.android.settings.fuelgauge.datasaver.DynamicDenylistManager; import com.android.settings.fuelgauge.datasaver.DynamicDenylistManager;
import com.android.settings.network.SubscriptionUtil; import com.android.settings.network.SubscriptionUtil;
import com.android.settings.widget.EntityHeaderController; import com.android.settings.overlay.FeatureFactory;
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.UidDetail; import com.android.settingslib.net.UidDetail;
import com.android.settingslib.net.UidDetailProvider; import com.android.settingslib.net.UidDetailProvider;
import com.android.settingslib.widget.IntroPreference;
import kotlin.Unit; import kotlin.Unit;
@@ -65,6 +67,8 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
private static final String TAG = "AppDataUsage"; private static final String TAG = "AppDataUsage";
static final String ARG_APP_ITEM = "app_item"; static final String ARG_APP_ITEM = "app_item";
@VisibleForTesting
static final String ARG_APP_HEADER = "app_header";
static final String ARG_NETWORK_TEMPLATE = "network_template"; static final String ARG_NETWORK_TEMPLATE = "network_template";
static final String ARG_NETWORK_CYCLES = "network_cycles"; static final String ARG_NETWORK_CYCLES = "network_cycles";
static final String ARG_SELECTED_CYCLE = "selected_cycle"; static final String ARG_SELECTED_CYCLE = "selected_cycle";
@@ -176,7 +180,7 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
removePreference(KEY_RESTRICT_BACKGROUND); removePreference(KEY_RESTRICT_BACKGROUND);
} }
addEntityHeader(); setupIntroPreference();
} }
@Override @Override
@@ -320,32 +324,32 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
} }
@VisibleForTesting @VisibleForTesting
void addEntityHeader() { void setupIntroPreference() {
String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null; final Preference pref = getPreferenceScreen().findPreference(ARG_APP_HEADER);
int uid = 0; if (pref != null) {
if (pkg != null) { pref.setIcon(mIcon);
pref.setTitle(mLabel);
pref.setSelectable(true);
}
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
if (!(preference instanceof IntroPreference)) return false;
String pkg = !mPackages.isEmpty() ? mPackages.valueAt(0) : null;
if (mAppItem.key > 0 && pkg != null) {
try { try {
uid = mPackageManager.getPackageUidAsUser(pkg, int uid = mPackageManager.getPackageUidAsUser(pkg,
UserHandle.getUserId(mAppItem.key)); UserHandle.getUserId(mAppItem.key));
startAppInfoSettings(pkg, uid, this, 0 /* request */,
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
.getMetricsCategory(this));
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Skipping UID because cannot find package " + pkg); Log.w(TAG, "Skipping UID because cannot find package " + pkg);
} }
} }
return true;
final boolean showInfoButton = mAppItem.key > 0;
final Activity activity = getActivity();
final Preference pref = EntityHeaderController
.newInstance(activity, this, null /* header */)
.setUid(uid)
.setHasAppInfoLink(showInfoButton)
.setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
EntityHeaderController.ActionType.ACTION_NONE)
.setIcon(mIcon)
.setLabel(mLabel)
.setPackageName(pkg)
.done(getPrefContext());
getPreferenceScreen().addPreference(pref);
} }
@Override @Override

View File

@@ -17,7 +17,6 @@
package com.android.settings.datausage package com.android.settings.datausage
import android.content.Context import android.content.Context
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -28,6 +27,7 @@ import com.android.settings.datausage.lib.NetworkUsageDetailsData
import com.android.settings.spa.preference.ComposePreferenceController import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.ui.Category
import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@@ -60,7 +60,7 @@ class AppDataUsageSummaryController(context: Context, preferenceKey: String) :
@Composable @Composable
override fun Content() { override fun Content() {
Column { Category {
val totalUsage by totalUsageFlow.collectAsStateWithLifecycle(emptyDataUsage) val totalUsage by totalUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)
val foregroundUsage by foregroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage) val foregroundUsage by foregroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)
val backgroundUsage by backgroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage) val backgroundUsage by backgroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)

View File

@@ -25,8 +25,10 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder; import androidx.preference.PreferenceViewHolder;
import com.android.settings.R; import com.android.settings.R;
import com.android.settingslib.widget.GroupSectionDividerMixin;
public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface { public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface,
GroupSectionDividerMixin {
private CycleAdapter mAdapter; private CycleAdapter mAdapter;
@Nullable @Nullable

View File

@@ -28,7 +28,7 @@ import com.android.settings.R
import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.widget.GroupSectionDividerMixin import com.android.settingslib.widget.GroupSectionDividerMixin
open class ComposeMainSwitchPreference @JvmOverloads constructor( open class ComposeGroupSectionPreference @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0, defStyleAttr: Int = 0,

View File

@@ -28,11 +28,13 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq; import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
@@ -42,6 +44,7 @@ import android.os.Bundle;
import android.os.Process; import android.os.Process;
import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.FeatureFlagUtils;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@@ -51,22 +54,20 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.AppInfoBase;
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.ShadowFragment; import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal; import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal;
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.RestrictedSwitchPreference; import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.AbstractPreferenceController;
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 com.android.settingslib.widget.IntroPreference;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Answers; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric; import org.robolectric.Robolectric;
@@ -79,27 +80,25 @@ import org.robolectric.util.ReflectionHelpers;
import java.util.List; import java.util.List;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowEntityHeaderController.class, ShadowRestrictedLockUtilsInternal.class}) @Config(shadows = {ShadowRestrictedLockUtilsInternal.class})
public class AppDataUsageTest { public class AppDataUsageTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private EntityHeaderController mHeaderController;
@Mock @Mock
private PackageManager mPackageManager; private PackageManager mPackageManager;
private IntroPreference mIntroPreference;
private AppDataUsage mFragment; private AppDataUsage mFragment;
private Context mContext;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
ShadowEntityHeaderController.setUseMock(mHeaderController); mContext = spy(RuntimeEnvironment.application);
when(mHeaderController.setUid(anyInt())).thenReturn(mHeaderController); mIntroPreference = new IntroPreference(mContext);
} FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_ENABLE_SPA, true);
@After
public void tearDown() {
ShadowEntityHeaderController.reset();
} }
@Test @Test
@@ -161,6 +160,7 @@ public class AppDataUsageTest {
} }
@Test @Test
@Config(shadows = ShadowFragment.class)
public void bindAppHeader_allWorkApps_shouldNotShowAppInfoLink() { public void bindAppHeader_allWorkApps_shouldNotShowAppInfoLink() {
mFragment = spy(new TestFragment()); mFragment = spy(new TestFragment());
@@ -169,12 +169,20 @@ public class AppDataUsageTest {
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
ReflectionHelpers.setField(mFragment, "mAppItem", mock(AppItem.class)); ReflectionHelpers.setField(mFragment, "mAppItem", mock(AppItem.class));
mFragment.addEntityHeader(); when(mFragment.getPreferenceScreen().findPreference(AppDataUsage.ARG_APP_HEADER))
.thenReturn(mIntroPreference);
when(mFragment.getContext()).thenReturn(mContext);
doNothing().when(mContext).startActivity(any());
verify(mHeaderController).setHasAppInfoLink(false); mFragment.setupIntroPreference();
mFragment.onPreferenceTreeClick(mIntroPreference);
verify(mFragment, never()).getActivity();
verify(mContext, never()).startActivity(any(Intent.class));
} }
@Test @Test
@Config(shadows = ShadowFragment.class)
public void bindAppHeader_workApp_shouldSetWorkAppUid() public void bindAppHeader_workApp_shouldSetWorkAppUid()
throws PackageManager.NameNotFoundException { throws PackageManager.NameNotFoundException {
final int fakeUserId = 100; final int fakeUserId = 100;
@@ -188,19 +196,21 @@ public class AppDataUsageTest {
ReflectionHelpers.setField(mFragment, "mAppItem", appItem); ReflectionHelpers.setField(mFragment, "mAppItem", appItem);
ReflectionHelpers.setField(mFragment, "mPackages", packages); ReflectionHelpers.setField(mFragment, "mPackages", packages);
when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())) when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(fakeUserId);
.thenReturn(fakeUserId);
when(mHeaderController.setHasAppInfoLink(anyBoolean())).thenReturn(mHeaderController);
when(mFragment.getPreferenceManager()) when(mFragment.getPreferenceManager())
.thenReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS)); .thenReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS));
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
mFragment.addEntityHeader(); when(mFragment.getPreferenceScreen().findPreference(AppDataUsage.ARG_APP_HEADER))
.thenReturn(mIntroPreference);
when(mFragment.getContext()).thenReturn(mContext);
doNothing().when(mContext).startActivity(any());
verify(mHeaderController).setHasAppInfoLink(true); mFragment.setupIntroPreference();
verify(mHeaderController).setUid(fakeUserId); mFragment.onPreferenceTreeClick(mIntroPreference);
ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext).startActivity(argumentCaptor.capture());
} }
@Test @Test