From 224c16db50cbfaa227d0e286922ca765516dfabb Mon Sep 17 00:00:00 2001 From: Yuchen Date: Fri, 13 Dec 2024 02:47:51 +0000 Subject: [PATCH 1/2] [Expressive design] Rename ComposeMainSwitchPreference to ComposeGroupSectionPreference. Bug: 349681531 Flag: EXEMPT migration Test: visual Change-Id: I5cf44a951894d56f0a7c5d2794442bfcdebb05d1 --- res/xml/mobile_network_settings.xml | 2 +- src/com/android/settings/spa/preference/ComposePreference.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml index a1a2276ee14..8c948ff11da 100644 --- a/res/xml/mobile_network_settings.xml +++ b/res/xml/mobile_network_settings.xml @@ -18,7 +18,7 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:key="mobile_network_pref_screen"> - diff --git a/src/com/android/settings/spa/preference/ComposePreference.kt b/src/com/android/settings/spa/preference/ComposePreference.kt index 91b2d8ac81a..57aa3866390 100644 --- a/src/com/android/settings/spa/preference/ComposePreference.kt +++ b/src/com/android/settings/spa/preference/ComposePreference.kt @@ -28,7 +28,7 @@ import com.android.settings.R import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.widget.GroupSectionDividerMixin -open class ComposeMainSwitchPreference @JvmOverloads constructor( +open class ComposeGroupSectionPreference @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, From c7635b17051db4b630f4e094444427548977dd78 Mon Sep 17 00:00:00 2001 From: Yuchen Date: Fri, 13 Dec 2024 06:56:00 +0000 Subject: [PATCH 2/2] [Expressive design] Update AppDataUsage. Migrate EntityHeader to IntroPReference. Remove background of SpinnerPreference. Bug: 349681531 Flag: EXEMPT migration Test: atest AppDataUsageTest Change-Id: Icb91c418a67489e3069f958356a9675f279ee5f8 --- res/xml/app_data_usage.xml | 6 +- .../settings/datausage/AppDataUsage.java | 48 ++++++++------- .../AppDataUsageSummaryController.kt | 4 +- .../settings/datausage/SpinnerPreference.java | 4 +- .../settings/datausage/AppDataUsageTest.java | 58 +++++++++++-------- 5 files changed, 70 insertions(+), 50 deletions(-) diff --git a/res/xml/app_data_usage.xml b/res/xml/app_data_usage.xml index 88f60ef6ca7..3135f597368 100644 --- a/res/xml/app_data_usage.xml +++ b/res/xml/app_data_usage.xml @@ -20,11 +20,15 @@ android:key="app_data_usage_screen" android:title="@string/data_usage_app_summary_title"> + + - diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java index 8480c5cc830..d8e238c15e2 100644 --- a/src/com/android/settings/datausage/AppDataUsage.java +++ b/src/com/android/settings/datausage/AppDataUsage.java @@ -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.getAppUidList; +import static com.android.settings.spa.app.appinfo.AppInfoSettingsProvider.startAppInfoSettings; import android.app.Activity; 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.fuelgauge.datasaver.DynamicDenylistManager; 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.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedSwitchPreference; import com.android.settingslib.net.UidDetail; import com.android.settingslib.net.UidDetailProvider; +import com.android.settingslib.widget.IntroPreference; import kotlin.Unit; @@ -65,6 +67,8 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC private static final String TAG = "AppDataUsage"; 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_CYCLES = "network_cycles"; static final String ARG_SELECTED_CYCLE = "selected_cycle"; @@ -176,7 +180,7 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC removePreference(KEY_RESTRICT_BACKGROUND); } - addEntityHeader(); + setupIntroPreference(); } @Override @@ -320,32 +324,32 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC } @VisibleForTesting - void addEntityHeader() { - String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null; - int uid = 0; - if (pkg != null) { + void setupIntroPreference() { + final Preference pref = getPreferenceScreen().findPreference(ARG_APP_HEADER); + if (pref != 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 { - uid = mPackageManager.getPackageUidAsUser(pkg, + int uid = mPackageManager.getPackageUidAsUser(pkg, UserHandle.getUserId(mAppItem.key)); + startAppInfoSettings(pkg, uid, this, 0 /* request */, + FeatureFactory.getFeatureFactory().getMetricsFeatureProvider() + .getMetricsCategory(this)); } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Skipping UID because cannot find package " + pkg); } } - - 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); + return true; } @Override diff --git a/src/com/android/settings/datausage/AppDataUsageSummaryController.kt b/src/com/android/settings/datausage/AppDataUsageSummaryController.kt index 233e1078812..fb7101dba9b 100644 --- a/src/com/android/settings/datausage/AppDataUsageSummaryController.kt +++ b/src/com/android/settings/datausage/AppDataUsageSummaryController.kt @@ -17,7 +17,6 @@ package com.android.settings.datausage import android.content.Context -import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue 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.settingslib.spa.widget.preference.Preference 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 kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map @@ -60,7 +60,7 @@ class AppDataUsageSummaryController(context: Context, preferenceKey: String) : @Composable override fun Content() { - Column { + Category { val totalUsage by totalUsageFlow.collectAsStateWithLifecycle(emptyDataUsage) val foregroundUsage by foregroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage) val backgroundUsage by backgroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage) diff --git a/src/com/android/settings/datausage/SpinnerPreference.java b/src/com/android/settings/datausage/SpinnerPreference.java index febdead8ea0..a1b0f905c8a 100644 --- a/src/com/android/settings/datausage/SpinnerPreference.java +++ b/src/com/android/settings/datausage/SpinnerPreference.java @@ -25,8 +25,10 @@ import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; 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; @Nullable diff --git a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java index 4b8c9de5349..ce3340117e0 100644 --- a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java +++ b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java @@ -28,11 +28,13 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; 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.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -42,6 +44,7 @@ import android.os.Bundle; import android.os.Process; import android.telephony.SubscriptionManager; import android.util.ArraySet; +import android.util.FeatureFlagUtils; import androidx.fragment.app.FragmentActivity; import androidx.preference.PreferenceManager; @@ -51,22 +54,20 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.settings.applications.AppInfoBase; import com.android.settings.testutils.FakeFeatureFactory; 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.ShadowRestrictedLockUtilsInternal; -import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.AppItem; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedSwitchPreference; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.net.UidDetail; import com.android.settingslib.net.UidDetailProvider; +import com.android.settingslib.widget.IntroPreference; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Answers; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; @@ -79,27 +80,25 @@ import org.robolectric.util.ReflectionHelpers; import java.util.List; @RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowEntityHeaderController.class, ShadowRestrictedLockUtilsInternal.class}) +@Config(shadows = {ShadowRestrictedLockUtilsInternal.class}) public class AppDataUsageTest { - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private EntityHeaderController mHeaderController; @Mock private PackageManager mPackageManager; + private IntroPreference mIntroPreference; + private AppDataUsage mFragment; + private Context mContext; + @Before public void setUp() { MockitoAnnotations.initMocks(this); - ShadowEntityHeaderController.setUseMock(mHeaderController); - when(mHeaderController.setUid(anyInt())).thenReturn(mHeaderController); - } - - @After - public void tearDown() { - ShadowEntityHeaderController.reset(); + mContext = spy(RuntimeEnvironment.application); + mIntroPreference = new IntroPreference(mContext); + FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_ENABLE_SPA, true); } @Test @@ -161,6 +160,7 @@ public class AppDataUsageTest { } @Test + @Config(shadows = ShadowFragment.class) public void bindAppHeader_allWorkApps_shouldNotShowAppInfoLink() { mFragment = spy(new TestFragment()); @@ -169,12 +169,20 @@ public class AppDataUsageTest { doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); 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 + @Config(shadows = ShadowFragment.class) public void bindAppHeader_workApp_shouldSetWorkAppUid() throws PackageManager.NameNotFoundException { final int fakeUserId = 100; @@ -188,19 +196,21 @@ public class AppDataUsageTest { ReflectionHelpers.setField(mFragment, "mAppItem", appItem); ReflectionHelpers.setField(mFragment, "mPackages", packages); - when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())) - .thenReturn(fakeUserId); - - when(mHeaderController.setHasAppInfoLink(anyBoolean())).thenReturn(mHeaderController); - + when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(fakeUserId); when(mFragment.getPreferenceManager()) .thenReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS)); 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); - verify(mHeaderController).setUid(fakeUserId); + mFragment.setupIntroPreference(); + mFragment.onPreferenceTreeClick(mIntroPreference); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mContext).startActivity(argumentCaptor.capture()); } @Test