From a94e8d0bc72ad4aae6a62cad3673b099d72dcd13 Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Thu, 17 Nov 2022 18:19:31 +0800 Subject: [PATCH 01/10] Avoid media transcode can be bypass guard of developer options When developer options switch is off, a user including guest user can still configure media transcoding settings via slices. Bug: 244569778 Test: manual Change-Id: I3d70045c2498e683bf615cbe521e2f98d50b7eec --- ...scodeDefaultOptionPreferenceController.java | 7 ++++++- ...nscodeDisableCachePreferenceController.java | 7 ++++++- ...nscodeGlobalTogglePreferenceController.java | 7 ++++++- ...nscodeNotificationPreferenceController.java | 7 ++++++- ...anscodeUserControlPreferenceController.java | 7 ++++++- ...eDefaultOptionPreferenceControllerTest.java | 16 ++++++++++++++-- ...deDisableCachePreferenceControllerTest.java | 16 ++++++++++++++-- ...deGlobalTogglePreferenceControllerTest.java | 18 ++++++++++++++++-- ...deNotificationPreferenceControllerTest.java | 17 +++++++++++++++-- ...odeUserControlPreferenceControllerTest.java | 16 ++++++++++++++-- 10 files changed, 103 insertions(+), 15 deletions(-) diff --git a/src/com/android/settings/development/transcode/TranscodeDefaultOptionPreferenceController.java b/src/com/android/settings/development/transcode/TranscodeDefaultOptionPreferenceController.java index 1c2fcd98410..a12aa642d7e 100644 --- a/src/com/android/settings/development/transcode/TranscodeDefaultOptionPreferenceController.java +++ b/src/com/android/settings/development/transcode/TranscodeDefaultOptionPreferenceController.java @@ -21,6 +21,7 @@ import android.os.SystemProperties; import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; +import com.android.settingslib.development.DevelopmentSettingsEnabler; /** * The controller (on the transcode settings screen) indicating that by default we assume that apps @@ -47,7 +48,11 @@ public class TranscodeDefaultOptionPreferenceController extends TogglePreference @Override public int getAvailabilityStatus() { - return AVAILABLE; + if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) { + return AVAILABLE; + } else { + return CONDITIONALLY_UNAVAILABLE; + } } @Override diff --git a/src/com/android/settings/development/transcode/TranscodeDisableCachePreferenceController.java b/src/com/android/settings/development/transcode/TranscodeDisableCachePreferenceController.java index c3b0624e9df..8229c98bfa2 100644 --- a/src/com/android/settings/development/transcode/TranscodeDisableCachePreferenceController.java +++ b/src/com/android/settings/development/transcode/TranscodeDisableCachePreferenceController.java @@ -22,6 +22,7 @@ import android.os.SystemProperties; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; +import com.android.settingslib.development.DevelopmentSettingsEnabler; /** * The controller (in the Media transcoding settings) indicating the user's preference to disable @@ -49,7 +50,11 @@ public class TranscodeDisableCachePreferenceController extends TogglePreferenceC @Override public int getAvailabilityStatus() { - return AVAILABLE; + if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) { + return AVAILABLE; + } else { + return CONDITIONALLY_UNAVAILABLE; + } } @Override diff --git a/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceController.java b/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceController.java index b2d446e0721..63572f711fd 100644 --- a/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceController.java +++ b/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceController.java @@ -21,6 +21,7 @@ import android.os.SystemProperties; import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; +import com.android.settingslib.development.DevelopmentSettingsEnabler; /** * The controller for the "Enabling transcoding for all apps" switch on the transcode settings @@ -37,7 +38,11 @@ public class TranscodeGlobalTogglePreferenceController extends TogglePreferenceC @Override public int getAvailabilityStatus() { - return AVAILABLE; + if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) { + return AVAILABLE; + } else { + return CONDITIONALLY_UNAVAILABLE; + } } @Override diff --git a/src/com/android/settings/development/transcode/TranscodeNotificationPreferenceController.java b/src/com/android/settings/development/transcode/TranscodeNotificationPreferenceController.java index 6b6692e5811..11c9c743279 100644 --- a/src/com/android/settings/development/transcode/TranscodeNotificationPreferenceController.java +++ b/src/com/android/settings/development/transcode/TranscodeNotificationPreferenceController.java @@ -22,6 +22,7 @@ import android.os.SystemProperties; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; +import com.android.settingslib.development.DevelopmentSettingsEnabler; /** * The controller (in the Media transcoding settings) indicating the user's preference to show @@ -49,7 +50,11 @@ public class TranscodeNotificationPreferenceController extends TogglePreferenceC @Override public int getAvailabilityStatus() { - return AVAILABLE; + if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) { + return AVAILABLE; + } else { + return CONDITIONALLY_UNAVAILABLE; + } } @Override diff --git a/src/com/android/settings/development/transcode/TranscodeUserControlPreferenceController.java b/src/com/android/settings/development/transcode/TranscodeUserControlPreferenceController.java index ce82293486c..377ff26dda3 100644 --- a/src/com/android/settings/development/transcode/TranscodeUserControlPreferenceController.java +++ b/src/com/android/settings/development/transcode/TranscodeUserControlPreferenceController.java @@ -21,6 +21,7 @@ import android.os.SystemProperties; import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; +import com.android.settingslib.development.DevelopmentSettingsEnabler; /** * The controller for the User's control (over other transcoding preferences) preference switch on @@ -47,7 +48,11 @@ public class TranscodeUserControlPreferenceController extends TogglePreferenceCo @Override public int getAvailabilityStatus() { - return AVAILABLE; + if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) { + return AVAILABLE; + } else { + return CONDITIONALLY_UNAVAILABLE; + } } @Override diff --git a/tests/unit/src/com/android/settings/development/transcode/TranscodeDefaultOptionPreferenceControllerTest.java b/tests/unit/src/com/android/settings/development/transcode/TranscodeDefaultOptionPreferenceControllerTest.java index e3fa0766acc..ecbae2d2c74 100644 --- a/tests/unit/src/com/android/settings/development/transcode/TranscodeDefaultOptionPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/development/transcode/TranscodeDefaultOptionPreferenceControllerTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.os.SystemProperties; +import android.provider.Settings; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -36,11 +37,14 @@ public class TranscodeDefaultOptionPreferenceControllerTest { "persist.sys.fuse.transcode_default"; private TranscodeDefaultOptionPreferenceController mUnderTest; + private Context mContext; @Before public void setUp() { - Context context = ApplicationProvider.getApplicationContext(); - mUnderTest = new TranscodeDefaultOptionPreferenceController(context, "some_key"); + mContext = ApplicationProvider.getApplicationContext(); + mUnderTest = new TranscodeDefaultOptionPreferenceController(mContext, "some_key"); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); } @Test @@ -74,4 +78,12 @@ public class TranscodeDefaultOptionPreferenceControllerTest { assertThat(mUnderTest.getAvailabilityStatus()).isEqualTo( BasePreferenceController.AVAILABLE); } + + @Test + public void getAvailabilityStatus_developerOptionFalse_shouldReturnUNAVAILABLE() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0); + assertThat(mUnderTest.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } } diff --git a/tests/unit/src/com/android/settings/development/transcode/TranscodeDisableCachePreferenceControllerTest.java b/tests/unit/src/com/android/settings/development/transcode/TranscodeDisableCachePreferenceControllerTest.java index d1a3355e36a..e7d1d4b1af2 100644 --- a/tests/unit/src/com/android/settings/development/transcode/TranscodeDisableCachePreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/development/transcode/TranscodeDisableCachePreferenceControllerTest.java @@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.os.SystemProperties; +import android.provider.Settings; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -36,11 +37,14 @@ import org.junit.runner.RunWith; public class TranscodeDisableCachePreferenceControllerTest { private TranscodeDisableCachePreferenceController mUnderTest; + private Context mContext; @Before public void setUp() { - Context context = ApplicationProvider.getApplicationContext(); - mUnderTest = new TranscodeDisableCachePreferenceController(context, "some_key"); + mContext = ApplicationProvider.getApplicationContext(); + mUnderTest = new TranscodeDisableCachePreferenceController(mContext, "some_key"); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); } @Test @@ -74,4 +78,12 @@ public class TranscodeDisableCachePreferenceControllerTest { assertThat(mUnderTest.getAvailabilityStatus()).isEqualTo( BasePreferenceController.AVAILABLE); } + + @Test + public void getAvailabilityStatus_developerOptionFalse_shouldReturnUNAVAILABLE() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0); + assertThat(mUnderTest.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } } diff --git a/tests/unit/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceControllerTest.java b/tests/unit/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceControllerTest.java index 7c0acd7bea6..0751fd9b211 100644 --- a/tests/unit/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceControllerTest.java @@ -20,10 +20,13 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.os.SystemProperties; +import android.provider.Settings; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.settings.core.BasePreferenceController; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,11 +37,14 @@ public class TranscodeGlobalTogglePreferenceControllerTest { private static final String TRANSCODE_ENABLED_PROP_KEY = "persist.sys.fuse.transcode_enabled"; private TranscodeGlobalTogglePreferenceController mController; + private Context mContext; @Before public void setUp() { - Context context = ApplicationProvider.getApplicationContext(); - mController = new TranscodeGlobalTogglePreferenceController(context, "test_key"); + mContext = ApplicationProvider.getApplicationContext(); + mController = new TranscodeGlobalTogglePreferenceController(mContext, "test_key"); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); } @Test @@ -75,4 +81,12 @@ public class TranscodeGlobalTogglePreferenceControllerTest { // Verify the system property was updated. assertThat(SystemProperties.getBoolean(TRANSCODE_ENABLED_PROP_KEY, true)).isFalse(); } + + @Test + public void getAvailabilityStatus_developerOptionFalse_shouldReturnUNAVAILABLE() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } } diff --git a/tests/unit/src/com/android/settings/development/transcode/TranscodeNotificationPreferenceControllerTest.java b/tests/unit/src/com/android/settings/development/transcode/TranscodeNotificationPreferenceControllerTest.java index 65bfbf571e2..d9ae0a48c5b 100644 --- a/tests/unit/src/com/android/settings/development/transcode/TranscodeNotificationPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/development/transcode/TranscodeNotificationPreferenceControllerTest.java @@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.os.SystemProperties; +import android.provider.Settings; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -36,10 +37,14 @@ import org.junit.runner.RunWith; public class TranscodeNotificationPreferenceControllerTest { private TranscodeNotificationPreferenceController mUnderTest; + private Context mContext; + @Before public void setUp() { - Context context = ApplicationProvider.getApplicationContext(); - mUnderTest = new TranscodeNotificationPreferenceController(context, "notification_key"); + mContext = ApplicationProvider.getApplicationContext(); + mUnderTest = new TranscodeNotificationPreferenceController(mContext, "notification_key"); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); } @Test @@ -73,4 +78,12 @@ public class TranscodeNotificationPreferenceControllerTest { assertThat(mUnderTest.getAvailabilityStatus()).isEqualTo( BasePreferenceController.AVAILABLE); } + + @Test + public void getAvailabilityStatus_developerOptionFalse_shouldReturnUNAVAILABLE() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0); + assertThat(mUnderTest.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } } diff --git a/tests/unit/src/com/android/settings/development/transcode/TranscodeUserControlPreferenceControllerTest.java b/tests/unit/src/com/android/settings/development/transcode/TranscodeUserControlPreferenceControllerTest.java index 63a6ac6cb22..6e4bfc834fb 100644 --- a/tests/unit/src/com/android/settings/development/transcode/TranscodeUserControlPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/development/transcode/TranscodeUserControlPreferenceControllerTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.os.SystemProperties; +import android.provider.Settings; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -36,11 +37,14 @@ public class TranscodeUserControlPreferenceControllerTest { "persist.sys.fuse.transcode_user_control"; private TranscodeUserControlPreferenceController mUnderTest; + private Context mContext; @Before public void setUp() { - Context context = ApplicationProvider.getApplicationContext(); - mUnderTest = new TranscodeUserControlPreferenceController(context, "some_key"); + mContext = ApplicationProvider.getApplicationContext(); + mUnderTest = new TranscodeUserControlPreferenceController(mContext, "some_key"); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); } @Test @@ -74,4 +78,12 @@ public class TranscodeUserControlPreferenceControllerTest { assertThat(mUnderTest.getAvailabilityStatus()).isEqualTo( BasePreferenceController.AVAILABLE); } + + @Test + public void getAvailabilityStatus_developerOptionFalse_shouldReturnUNAVAILABLE() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0); + assertThat(mUnderTest.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } } From 254ea795652f877529485705e6b34dbef9bfc9e8 Mon Sep 17 00:00:00 2001 From: Dmitry Dementyev Date: Wed, 23 Nov 2022 13:26:15 -0800 Subject: [PATCH 02/10] Show account name as a single line string. Bug: 213905884 Test: manual Change-Id: I608dad01cb6bfd94a5b4318169cc50f9790a3fb2 --- src/com/android/settings/accounts/AccountTypePreference.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/android/settings/accounts/AccountTypePreference.java b/src/com/android/settings/accounts/AccountTypePreference.java index 8df576753d9..72c8c67a2df 100644 --- a/src/com/android/settings/accounts/AccountTypePreference.java +++ b/src/com/android/settings/accounts/AccountTypePreference.java @@ -86,6 +86,7 @@ public class AccountTypePreference extends AppPreference implements OnPreference setKey(buildKey(account)); setTitle(mTitle); + setSingleLineTitle(true); setSummary(summary); setIcon(icon); From 434c8934c4aa416931a66626016d94712e47d617 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Mon, 21 Nov 2022 15:04:24 +0800 Subject: [PATCH 03/10] Settings 2-pane deep link vulnerabilities Settings app must not start an deep link Activity if 1. The deep link Activity is not exported. or 2. Calling package does not have the permission to start the deep link Activity. Bug: 250589026 Test: make RunSettingsRoboTests ROBOTEST_FILTER=SettingsHomepageActivityTest Change-Id: I9a3bddfa5d9d1d2e924dd6f3e5e07dca6c11664f Merged-In: I9a3bddfa5d9d1d2e924dd6f3e5e07dca6c11664f --- .../homepage/SettingsHomepageActivity.java | 36 +++++++++++++++++++ .../SettingsHomepageActivityTest.java | 35 ++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 8e14c5a44ea..467f0aa10fe 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -27,6 +27,8 @@ import android.app.ActivityManager; import android.app.settings.SettingsEnums; import android.content.ComponentName; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.os.Bundle; @@ -43,6 +45,7 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.Toolbar; +import androidx.annotation.VisibleForTesting; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowCompat; @@ -65,6 +68,7 @@ import com.android.settings.core.CategoryMixin; import com.android.settings.core.FeatureFlags; import com.android.settings.homepage.contextualcards.ContextualCardsFragment; import com.android.settings.overlay.FeatureFactory; +import com.android.settings.password.PasswordUtils; import com.android.settingslib.Utils; import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; @@ -431,6 +435,32 @@ public class SettingsHomepageActivity extends FragmentActivity implements finish(); return; } + + if (!TextUtils.equals(PasswordUtils.getCallingAppPackageName(getActivityToken()), + getPackageName())) { + ActivityInfo targetActivityInfo = null; + try { + targetActivityInfo = getPackageManager().getActivityInfo(targetComponentName, + /* flags= */ 0); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to get target ActivityInfo: " + e); + finish(); + return; + } + + if (!targetActivityInfo.exported) { + Log.e(TAG, "Must not launch an unexported Actvity for deep link"); + finish(); + return; + } + + if (!isCallingAppPermitted(targetActivityInfo.permission)) { + Log.e(TAG, "Calling app must have the permission of deep link Activity"); + finish(); + return; + } + } + targetIntent.setComponent(targetComponentName); // To prevent launchDeepLinkIntentToRight again for configuration change. @@ -472,6 +502,12 @@ public class SettingsHomepageActivity extends FragmentActivity implements } } + @VisibleForTesting + boolean isCallingAppPermitted(String permission) { + return TextUtils.isEmpty(permission) || PasswordUtils.isCallingAppPermitted( + this, getActivityToken(), permission); + } + private String getHighlightMenuKey() { final Intent intent = getIntent(); if (intent != null && TextUtils.equals(intent.getAction(), diff --git a/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java b/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java index 4d203a8a6b0..4de8b005c3c 100644 --- a/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java +++ b/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java @@ -20,6 +20,8 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -37,9 +39,11 @@ import androidx.fragment.app.Fragment; import com.android.settings.R; import com.android.settings.dashboard.suggestions.SuggestionFeatureProviderImpl; import com.android.settings.homepage.contextualcards.slices.BatteryFixSliceTest; +import com.android.settings.testutils.shadow.ShadowPasswordUtils; import com.android.settings.testutils.shadow.ShadowUserManager; import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -66,6 +70,11 @@ public class SettingsHomepageActivityTest { MockitoAnnotations.initMocks(this); } + @After + public void tearDown() { + ShadowPasswordUtils.reset(); + } + @Test public void launch_shouldHaveAnimationForIaFragment() { final SettingsHomepageActivity activity = Robolectric.buildActivity( @@ -195,6 +204,32 @@ public class SettingsHomepageActivityTest { & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS).isEqualTo(0); } + @Test + @Config(shadows = {ShadowPasswordUtils.class}) + public void isCallingAppPermitted_emptyPermission_returnTrue() { + SettingsHomepageActivity homepageActivity = spy(new SettingsHomepageActivity()); + + assertTrue(homepageActivity.isCallingAppPermitted("")); + } + + @Test + @Config(shadows = {ShadowPasswordUtils.class}) + public void isCallingAppPermitted_noGrantedPermission_returnFalse() { + SettingsHomepageActivity homepageActivity = spy(new SettingsHomepageActivity()); + + assertFalse(homepageActivity.isCallingAppPermitted("android.permission.TEST")); + } + + @Test + @Config(shadows = {ShadowPasswordUtils.class}) + public void isCallingAppPermitted_grantedPermission_returnTrue() { + SettingsHomepageActivity homepageActivity = spy(new SettingsHomepageActivity()); + String permission = "android.permission.TEST"; + ShadowPasswordUtils.addGrantedPermission(permission); + + assertTrue(homepageActivity.isCallingAppPermitted(permission)); + } + @Implements(SuggestionFeatureProviderImpl.class) public static class ShadowSuggestionFeatureProviderImpl { From 5ced0af08b331e4f92341a6e92d26041beb5a849 Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Wed, 30 Nov 2022 07:22:38 +0000 Subject: [PATCH 04/10] [Settings] Remove some warning message Replace startActivity by startActivityAsUser(UserHandle.SYSTEM) to reduce some warning log created for SIM detection. Bug: 260806431 Test: local, auto test Change-Id: Ib571bf9db922ec27d2229e8a283768c12ba61430 --- .../settings/sim/receivers/SimSlotChangeHandler.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java b/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java index 4a6b96c1fa3..f6c9cc42e15 100644 --- a/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java +++ b/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Looper; +import android.os.UserHandle; import android.provider.Settings; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -344,20 +345,20 @@ public class SimSlotChangeHandler { Intent intent = ChooseSimActivity.getIntent(mContext); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(ChooseSimActivity.KEY_HAS_PSIM, psimInserted); - mContext.startActivity(intent); + mContext.startActivityAsUser(intent, UserHandle.SYSTEM); } private void startSwitchSlotConfirmDialogActivity(SubscriptionInfo subscriptionInfo) { Intent intent = new Intent(mContext, SwitchToEsimConfirmDialogActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(SwitchToEsimConfirmDialogActivity.KEY_SUB_TO_ENABLE, subscriptionInfo); - mContext.startActivity(intent); + mContext.startActivityAsUser(intent, UserHandle.SYSTEM); } private void startDsdsDialogActivity() { Intent intent = new Intent(mContext, DsdsDialogActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(intent); + mContext.startActivityAsUser(intent, UserHandle.SYSTEM); } private void startSimConfirmDialogActivity(int subId) { @@ -368,7 +369,7 @@ public class SimSlotChangeHandler { Log.d(TAG, "Start ToggleSubscriptionDialogActivity with " + subId + " under DSDS+Mep."); Intent intent = ToggleSubscriptionDialogActivity.getIntent(mContext, subId, true); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(intent); + mContext.startActivityAsUser(intent, UserHandle.SYSTEM); } private boolean isMultipleEnabledProfilesSupported() { From 0ce8a8993e64d990b3f5cee0f92cba98b63d188f Mon Sep 17 00:00:00 2001 From: Zekan Qian Date: Wed, 30 Nov 2022 16:09:12 +0800 Subject: [PATCH 05/10] Use SpaIntent util in Settings. Bug: 244122804 Test: unit-test Change-Id: I5b2d02d102c87620331d4e91e654e848f5a8df6a --- src/com/android/settings/spa/SpaActivity.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/spa/SpaActivity.kt b/src/com/android/settings/spa/SpaActivity.kt index 896f899ad0a..838d6a1dc93 100644 --- a/src/com/android/settings/spa/SpaActivity.kt +++ b/src/com/android/settings/spa/SpaActivity.kt @@ -19,14 +19,14 @@ package com.android.settings.spa import android.content.Context import android.content.Intent import com.android.settingslib.spa.framework.BrowseActivity +import com.android.settingslib.spa.framework.util.appendSpaParams class SpaActivity : BrowseActivity() { companion object { @JvmStatic fun startSpaActivity(context: Context, destination: String) { - val intent = Intent(context, SpaActivity::class.java).apply { - putExtra(KEY_DESTINATION, destination) - } + val intent = Intent(context, SpaActivity::class.java) + .appendSpaParams(destination = destination) context.startActivity(intent) } } From 2df165a58aa8d485e4545f633e99a5988597a6bf Mon Sep 17 00:00:00 2001 From: Vania Januar Date: Thu, 20 Oct 2022 14:44:59 +0100 Subject: [PATCH 06/10] Add a stylus controller to Bluetooth Device Details. Feature is currently flagged behing SETTINGS_SHOW_STYLUS_PREFERENCES. Bug: 251201006 Test: SettingsRoboTests StylusDevicesControllerTest DD: go/stylus-connected-devices-doc Change-Id: I438b7fe5ca1c94f9dfb506c8918d0e6cb005ca33 --- res/drawable/ic_article.xml | 25 ++ res/drawable/ic_block.xml | 25 ++ res/drawable/ic_text_fields_alt.xml | 25 ++ res/values/strings.xml | 11 + res/xml/bluetooth_device_details_fragment.xml | 7 +- .../BluetoothDeviceDetailsFragment.java | 49 ++++ .../stylus/StylusDevicesController.java | 199 +++++++++++++++ .../BluetoothDeviceDetailsFragmentTest.java | 132 ++++++++-- .../stylus/StylusDevicesControllerTest.java | 233 ++++++++++++++++++ 9 files changed, 687 insertions(+), 19 deletions(-) create mode 100644 res/drawable/ic_article.xml create mode 100644 res/drawable/ic_block.xml create mode 100644 res/drawable/ic_text_fields_alt.xml create mode 100644 src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java create mode 100644 tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java diff --git a/res/drawable/ic_article.xml b/res/drawable/ic_article.xml new file mode 100644 index 00000000000..0c32c0f6c56 --- /dev/null +++ b/res/drawable/ic_article.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_block.xml b/res/drawable/ic_block.xml new file mode 100644 index 00000000000..cf73681e3f5 --- /dev/null +++ b/res/drawable/ic_block.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_text_fields_alt.xml b/res/drawable/ic_text_fields_alt.xml new file mode 100644 index 00000000000..673c0eba78c --- /dev/null +++ b/res/drawable/ic_text_fields_alt.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index d91cb88c499..91f6f68b6e8 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -228,6 +228,17 @@ See all + + Stylus + + System note taking app + + Stylus writing in textfields + + Ignore all stylus button presses + + USI stylus + Date & time diff --git a/res/xml/bluetooth_device_details_fragment.xml b/res/xml/bluetooth_device_details_fragment.xml index 0528973d676..5cd7c3a8e3d 100644 --- a/res/xml/bluetooth_device_details_fragment.xml +++ b/res/xml/bluetooth_device_details_fragment.xml @@ -44,15 +44,18 @@ + android:gravity="center"/> + android:layout_height="8dp"/> + + roleHolders = rm.getRoleHoldersAsUser(RoleManager.ROLE_ASSISTANT, + mContext.getUser()); + if (roleHolders.isEmpty()) { + return null; + } + + String packageName = roleHolders.get(0); + PackageManager pm = mContext.getPackageManager(); + String appName = packageName; + try { + ApplicationInfo ai = pm.getApplicationInfo(packageName, + PackageManager.ApplicationInfoFlags.of(0)); + appName = ai == null ? packageName : pm.getApplicationLabel(ai).toString(); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Notes role package not found."); + } + + Preference pref = new Preference(mContext); + pref.setKey(KEY_DEFAULT_NOTES); + pref.setTitle(mContext.getString(R.string.stylus_default_notes_app)); + pref.setIcon(R.drawable.ic_article); + pref.setEnabled(true); + pref.setSummary(appName); + return pref; + } + + private SwitchPreference createHandwritingPreference() { + SwitchPreference pref = new SwitchPreference(mContext); + pref.setKey(KEY_HANDWRITING); + pref.setTitle(mContext.getString(R.string.stylus_textfield_handwriting)); + pref.setIcon(R.drawable.ic_text_fields_alt); + pref.setOnPreferenceClickListener(this); + pref.setChecked(Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.STYLUS_HANDWRITING_ENABLED, 0) == 1); + return pref; + } + + private SwitchPreference createButtonPressPreference() { + SwitchPreference pref = new SwitchPreference(mContext); + pref.setKey(KEY_IGNORE_BUTTON); + pref.setTitle(mContext.getString(R.string.stylus_ignore_button)); + pref.setIcon(R.drawable.ic_block); + pref.setOnPreferenceClickListener(this); + return pref; + } + + @Override + public boolean onPreferenceClick(Preference preference) { + String key = preference.getKey(); + + switch (key) { + case KEY_DEFAULT_NOTES: + PackageManager pm = mContext.getPackageManager(); + String packageName = pm.getPermissionControllerPackageName(); + // TODO(b/254834764): replace with notes role once merged + Intent intent = new Intent(Intent.ACTION_MANAGE_DEFAULT_APP).setPackage( + packageName).putExtra(Intent.EXTRA_ROLE_NAME, RoleManager.ROLE_ASSISTANT); + mContext.startActivity(intent); + break; + case KEY_HANDWRITING: + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.STYLUS_HANDWRITING_ENABLED, + ((SwitchPreference) preference).isChecked() ? 1 : 0); + break; + case KEY_IGNORE_BUTTON: + // TODO(b/251199452): to turn off stylus button presses + break; + } + return true; + } + + @Override + public final void displayPreference(PreferenceScreen screen) { + mPreferencesContainer = (PreferenceCategory) screen.findPreference(getPreferenceKey()); + super.displayPreference(screen); + + refresh(); + } + + @Override + public String getPreferenceKey() { + return KEY_STYLUS; + } + + @Override + public void onResume() { + refresh(); + } + + private void refresh() { + if (!isAvailable()) return; + + if (mInputDevice.getBluetoothAddress() != null) { + Preference notesPref = mPreferencesContainer.findPreference(KEY_DEFAULT_NOTES); + if (notesPref == null) { + notesPref = createDefaultNotesPreference(); + if (notesPref != null) { + mPreferencesContainer.addPreference(notesPref); + } + } + } + + Preference handwritingPref = mPreferencesContainer.findPreference(KEY_HANDWRITING); + // TODO(b/255732419): add proper InputMethodInfo conditional to show or hide + // InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); + if (handwritingPref == null) { + mPreferencesContainer.addPreference(createHandwritingPreference()); + } + + Preference buttonPref = mPreferencesContainer.findPreference(KEY_IGNORE_BUTTON); + if (buttonPref == null) { + mPreferencesContainer.addPreference(createButtonPressPreference()); + } + } +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java index d501c154213..57af4126540 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java @@ -25,16 +25,21 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.hardware.input.InputManager; import android.os.Bundle; import android.os.UserManager; +import android.util.FeatureFlagUtils; +import android.view.InputDevice; import android.view.MenuInflater; import android.view.MenuItem; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import androidx.preference.PreferenceScreen; @@ -51,6 +56,7 @@ import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @@ -68,6 +74,7 @@ public class BluetoothDeviceDetailsFragmentTest { private RoboMenu mMenu; private MenuInflater mInflater; private FragmentTransaction mFragmentTransaction; + private FragmentActivity mActivity; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private CachedBluetoothDevice mCachedDevice; @@ -77,29 +84,18 @@ public class BluetoothDeviceDetailsFragmentTest { private PreferenceScreen mPreferenceScreen; @Mock private UserManager mUserManager; + @Mock + private InputManager mInputManager; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + doReturn(mInputManager).when(mContext).getSystemService(InputManager.class); + removeInputDeviceWithMatchingBluetoothAddress(); FakeFeatureFactory.setupForTest(); - mFragment = spy(BluetoothDeviceDetailsFragment.newInstance(TEST_ADDRESS)); - doReturn(mLocalManager).when(mFragment).getLocalBluetoothManager(any()); - doReturn(mCachedDevice).when(mFragment).getCachedDevice(any()); - doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); - doReturn(mUserManager).when(mFragment).getUserManager(); - - FragmentManager fragmentManager = mock(FragmentManager.class); - when(mFragment.getFragmentManager()).thenReturn(fragmentManager); - mFragmentTransaction = mock(FragmentTransaction.class); - when(fragmentManager.beginTransaction()).thenReturn(mFragmentTransaction); - - when(mCachedDevice.getAddress()).thenReturn(TEST_ADDRESS); - when(mCachedDevice.getIdentityAddress()).thenReturn(TEST_ADDRESS); - Bundle args = new Bundle(); - args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS, TEST_ADDRESS); - mFragment.setArguments(args); + mFragment = setupFragment(); mFragment.onAttach(mContext); mMenu = new RoboMenu(mContext); @@ -111,6 +107,43 @@ public class BluetoothDeviceDetailsFragmentTest { assertThat(mFragment.mDeviceAddress).isEqualTo(TEST_ADDRESS); assertThat(mFragment.mManager).isEqualTo(mLocalManager); assertThat(mFragment.mCachedDevice).isEqualTo(mCachedDevice); + assertThat(mFragment.mInputDevice).isEqualTo(null); + } + + @Test + public void verifyOnAttachResult_flagEnabledAndInputDeviceSet_returnsInputDevice() { + FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES, + true); + InputDevice inputDevice = createInputDeviceWithMatchingBluetoothAddress(); + BluetoothDeviceDetailsFragment fragment = setupFragment(); + FragmentActivity activity = mock(FragmentActivity.class); + doReturn(inputDevice).when(fragment).getInputDevice(any()); + doReturn(activity).when(fragment).getActivity(); + + fragment.onAttach(mContext); + + assertThat(fragment.mDeviceAddress).isEqualTo(TEST_ADDRESS); + assertThat(fragment.mManager).isEqualTo(mLocalManager); + assertThat(fragment.mCachedDevice).isEqualTo(mCachedDevice); + assertThat(fragment.mInputDevice).isEqualTo(inputDevice); + } + + @Test + public void verifyOnAttachResult_flagDisabled_returnsNullInputDevice() { + FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES, + false); + InputDevice inputDevice = createInputDeviceWithMatchingBluetoothAddress(); + BluetoothDeviceDetailsFragment fragment = setupFragment(); + FragmentActivity activity = mock(FragmentActivity.class); + doReturn(inputDevice).when(fragment).getInputDevice(any()); + doReturn(activity).when(fragment).getActivity(); + + fragment.onAttach(mContext); + + assertThat(fragment.mDeviceAddress).isEqualTo(TEST_ADDRESS); + assertThat(fragment.mManager).isEqualTo(mLocalManager); + assertThat(fragment.mCachedDevice).isEqualTo(mCachedDevice); + assertThat(fragment.mInputDevice).isEqualTo(null); } @Test @@ -122,6 +155,31 @@ public class BluetoothDeviceDetailsFragmentTest { assertThat(item.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_rename_button)); } + @Test + public void getTitle_inputDeviceTitle() { + FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES, + true); + doReturn(mock(InputDevice.class)).when(mFragment).getInputDevice(mContext); + mFragment.onAttach(mContext); + + mFragment.setTitleForInputDevice(); + + assertThat(mActivity.getTitle().toString()).isEqualTo( + mContext.getString(R.string.stylus_device_details_title)); + } + + @Test + public void getTitle_inputDeviceNull_doesNotSetTitle() { + FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES, + true); + doReturn(null).when(mFragment).getInputDevice(mContext); + mFragment.onAttach(mContext); + + mFragment.setTitleForInputDevice(); + + verify(mActivity, times(0)).setTitle(any()); + } + @Test public void editMenu_clicked_showDialog() { mFragment.onCreateOptionsMenu(mMenu, mInflater); @@ -131,7 +189,7 @@ public class BluetoothDeviceDetailsFragmentTest { mFragment.onOptionsItemSelected(item); assertThat(item.getItemId()) - .isEqualTo(BluetoothDeviceDetailsFragment.EDIT_DEVICE_NAME_ITEM_ID); + .isEqualTo(BluetoothDeviceDetailsFragment.EDIT_DEVICE_NAME_ITEM_ID); verify(mFragmentTransaction).add(captor.capture(), eq(RemoteDeviceNameDialogFragment.TAG)); RemoteDeviceNameDialogFragment dialog = (RemoteDeviceNameDialogFragment) captor.getValue(); assertThat(dialog).isNotNull(); @@ -145,4 +203,44 @@ public class BluetoothDeviceDetailsFragmentTest { verify(mFragment).finish(); } + + private InputDevice createInputDeviceWithMatchingBluetoothAddress() { + doReturn(new int[]{0}).when(mInputManager).getInputDeviceIds(); + InputDevice device = mock(InputDevice.class); + doReturn(TEST_ADDRESS).when(mInputManager).getInputDeviceBluetoothAddress(0); + doReturn(device).when(mInputManager).getInputDevice(0); + return device; + } + + private InputDevice removeInputDeviceWithMatchingBluetoothAddress() { + doReturn(new int[]{0}).when(mInputManager).getInputDeviceIds(); + doReturn(null).when(mInputManager).getInputDeviceBluetoothAddress(0); + return null; + } + + private BluetoothDeviceDetailsFragment setupFragment() { + BluetoothDeviceDetailsFragment fragment = spy( + BluetoothDeviceDetailsFragment.newInstance(TEST_ADDRESS)); + doReturn(mLocalManager).when(fragment).getLocalBluetoothManager(any()); + doReturn(mCachedDevice).when(fragment).getCachedDevice(any()); + doReturn(mPreferenceScreen).when(fragment).getPreferenceScreen(); + doReturn(mUserManager).when(fragment).getUserManager(); + + mActivity = spy(Robolectric.setupActivity(FragmentActivity.class)); + doReturn(mActivity).when(fragment).getActivity(); + doReturn(mContext).when(fragment).getContext(); + + FragmentManager fragmentManager = mock(FragmentManager.class); + doReturn(fragmentManager).when(fragment).getFragmentManager(); + mFragmentTransaction = mock(FragmentTransaction.class); + doReturn(mFragmentTransaction).when(fragmentManager).beginTransaction(); + + doReturn(TEST_ADDRESS).when(mCachedDevice).getAddress(); + doReturn(TEST_ADDRESS).when(mCachedDevice).getIdentityAddress(); + Bundle args = new Bundle(); + args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS, TEST_ADDRESS); + fragment.setArguments(args); + + return fragment; + } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java new file mode 100644 index 00000000000..f704f2d7a0b --- /dev/null +++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2022 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.connecteddevice.stylus; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.role.RoleManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.provider.Settings; +import android.view.InputDevice; +import android.view.inputmethod.InputMethodManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; + +import java.util.Collections; + +@RunWith(RobolectricTestRunner.class) +public class StylusDevicesControllerTest { + private static final String NOTES_PACKAGE_NAME = "notes.package"; + private static final CharSequence NOTES_APP_LABEL = "App Label"; + + private Context mContext; + private StylusDevicesController mController; + private PreferenceCategory mPreferenceContainer; + private PreferenceScreen mScreen; + private InputDevice mInputDevice; + + @Mock + private InputMethodManager mImm; + @Mock + private PackageManager mPm; + @Mock + private RoleManager mRm; + @Mock + private Lifecycle mLifecycle; + + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = spy(ApplicationProvider.getApplicationContext()); + PreferenceManager preferenceManager = new PreferenceManager(mContext); + mScreen = preferenceManager.createPreferenceScreen(mContext); + mPreferenceContainer = new PreferenceCategory(mContext); + mPreferenceContainer.setKey(StylusDevicesController.KEY_STYLUS); + mScreen.addPreference(mPreferenceContainer); + + when(mContext.getSystemService(InputMethodManager.class)).thenReturn(mImm); + when(mContext.getSystemService(RoleManager.class)).thenReturn(mRm); + doNothing().when(mContext).startActivity(any()); + + // TODO(b/254834764): notes role placeholder + when(mRm.getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any(UserHandle.class))) + .thenReturn(Collections.singletonList(NOTES_PACKAGE_NAME)); + when(mContext.getPackageManager()).thenReturn(mPm); + when(mPm.getApplicationInfo(eq(NOTES_PACKAGE_NAME), + any(PackageManager.ApplicationInfoFlags.class))).thenReturn(new ApplicationInfo()); + when(mPm.getApplicationLabel(any(ApplicationInfo.class))).thenReturn(NOTES_APP_LABEL); + + mInputDevice = spy(new InputDevice.Builder() + .setId(1) + .setSources(InputDevice.SOURCE_STYLUS) + .build()); + when(mInputDevice.getBluetoothAddress()).thenReturn("SOME:ADDRESS"); + + mController = new StylusDevicesController(mContext, mInputDevice, mLifecycle); + } + + @Test + public void noInputDevice_noPreference() { + StylusDevicesController controller = new StylusDevicesController( + mContext, null, mLifecycle + ); + showScreen(controller); + assertThat(mPreferenceContainer.getPreferenceCount()).isEqualTo(0); + } + + @Test + public void btStylusInputDevice_showsAllPreferences() { + showScreen(mController); + Preference defaultNotesPref = mPreferenceContainer.getPreference(0); + Preference handwritingPref = mPreferenceContainer.getPreference(1); + Preference buttonPref = mPreferenceContainer.getPreference(2); + + assertThat(mPreferenceContainer.getPreferenceCount()).isEqualTo(3); + assertThat(defaultNotesPref.getTitle().toString()).isEqualTo( + mContext.getString(R.string.stylus_default_notes_app)); + assertThat(handwritingPref.getTitle().toString()).isEqualTo( + mContext.getString(R.string.stylus_textfield_handwriting)); + assertThat(buttonPref.getTitle().toString()).isEqualTo( + mContext.getString(R.string.stylus_ignore_button)); + } + + @Test + @Ignore // TODO(b/255732419): unignore when InputMethodInfo available + public void btStylusInputDevice_noHandwritingIme_showsSomePreferences() { + showScreen(mController); + Preference defaultNotesPref = mPreferenceContainer.getPreference(0); + Preference buttonPref = mPreferenceContainer.getPreference(1); + + assertThat(mPreferenceContainer.getPreferenceCount()).isEqualTo(2); + assertThat(defaultNotesPref.getTitle().toString()).isEqualTo( + mContext.getString(R.string.stylus_default_notes_app)); + assertThat(buttonPref.getTitle().toString()).isEqualTo( + mContext.getString(R.string.stylus_ignore_button)); + } + + @Test + public void defaultNotesPreference_showsNotesRoleApp() { + showScreen(mController); + Preference defaultNotesPref = mPreferenceContainer.getPreference(0); + + assertThat(defaultNotesPref.getTitle().toString()).isEqualTo( + mContext.getString(R.string.stylus_default_notes_app)); + assertThat(defaultNotesPref.getSummary().toString()).isEqualTo(NOTES_APP_LABEL.toString()); + } + + @Test + public void defaultNotesPreference_noRoleHolder_hidesNotesRoleApp() { + // TODO(b/254834764): replace with notes role once merged + when(mRm.getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any(UserHandle.class))) + .thenReturn(Collections.emptyList()); + showScreen(mController); + + for (int i = 0; i < mPreferenceContainer.getPreferenceCount(); i++) { + Preference pref = mPreferenceContainer.getPreference(i); + assertThat(pref.getTitle().toString()).isNotEqualTo( + mContext.getString(R.string.stylus_default_notes_app)); + } + } + + @Test + public void defaultNotesPreferenceClick_sendsManageDefaultRoleIntent() { + final String permissionPackageName = "permissions.package"; + when(mPm.getPermissionControllerPackageName()).thenReturn(permissionPackageName); + final ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + + showScreen(mController); + Preference defaultNotesPref = mPreferenceContainer.getPreference(0); + mController.onPreferenceClick(defaultNotesPref); + verify(mContext).startActivity(captor.capture()); + + Intent intent = captor.getValue(); + assertThat(intent.getAction()).isEqualTo(Intent.ACTION_MANAGE_DEFAULT_APP); + assertThat(intent.getPackage()).isEqualTo(permissionPackageName); + // TODO(b/254834764): when notes role is merged + assertThat(intent.getStringExtra(Intent.EXTRA_ROLE_NAME)).isEqualTo( + RoleManager.ROLE_ASSISTANT); + } + + @Test + public void handwritingPreference_checkedWhenFlagTrue() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.STYLUS_HANDWRITING_ENABLED, 1); + + showScreen(mController); + SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1); + + assertThat(handwritingPref.isChecked()).isEqualTo(true); + } + + @Test + public void handwritingPreference_uncheckedWhenFlagFalse() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.STYLUS_HANDWRITING_ENABLED, 0); + + showScreen(mController); + SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1); + + assertThat(handwritingPref.isChecked()).isEqualTo(false); + } + + @Test + public void handwritingPreference_updatesFlagOnClick() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.STYLUS_HANDWRITING_ENABLED, 0); + showScreen(mController); + SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1); + + handwritingPref.performClick(); + + assertThat(handwritingPref.isChecked()).isEqualTo(true); + assertThat(Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.STYLUS_HANDWRITING_ENABLED, -1)).isEqualTo(1); + } + + private void showScreen(StylusDevicesController controller) { + controller.displayPreference(mScreen); + } +} From bfacd938f6486cc210fd7adb59af1319b1dd0850 Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Tue, 29 Nov 2022 12:20:20 +0000 Subject: [PATCH 07/10] [Settings] Code refactor Create PreferenceController for each of the Preference presented on screen, which allows SettingsFragment better control the behavior of Preference. Bug: 260540995 Test: auto and local test Change-Id: Iadd9c10fb353d6304d61e65687a505c71a53179a --- .../aboutphone/MyDeviceInfoFragment.java | 12 +++++- .../SimStatusPreferenceController.java | 42 ++++++++++++++++--- .../SimStatusPreferenceControllerTest.java | 40 +++++++++++++----- 3 files changed, 76 insertions(+), 18 deletions(-) diff --git a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java index 4af5d79c37f..c048ed5d1f9 100644 --- a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java +++ b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java @@ -103,7 +103,17 @@ public class MyDeviceInfoFragment extends DashboardFragment private static List buildPreferenceControllers( Context context, MyDeviceInfoFragment fragment, Lifecycle lifecycle) { final List controllers = new ArrayList<>(); - controllers.add(new SimStatusPreferenceController(context, fragment)); + + SimStatusPreferenceController defaultRecord = + new SimStatusPreferenceController(context, fragment); + + for (int slotIndex = 0; slotIndex < defaultRecord.getSimSlotSize(); slotIndex ++) { + SimStatusPreferenceController slotRecord = + new SimStatusPreferenceController(context, fragment); + slotRecord.setSimSlotStatus(slotIndex); + controllers.add(slotRecord); + } + controllers.add(new IpAddressPreferenceController(context, lifecycle)); controllers.add(new WifiMacAddressPreferenceController(context, lifecycle)); controllers.add(new BluetoothAddressPreferenceController(context, lifecycle)); diff --git a/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java b/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java index f811d2a86d2..835f8203fb5 100644 --- a/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java +++ b/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java @@ -46,6 +46,8 @@ public class SimStatusPreferenceController extends private final Fragment mFragment; private final List mPreferenceList = new ArrayList<>(); + private int mSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX; + public SimStatusPreferenceController(Context context, Fragment fragment) { super(context); @@ -57,9 +59,36 @@ public class SimStatusPreferenceController extends @Override public String getPreferenceKey() { + if (mSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX) { + return KEY_SIM_STATUS + mSlotIndex; + } return KEY_SIM_STATUS; } + /** + * Update the index of slot for this subscription. + * @param slotIndex index of slot + */ + public void setSimSlotStatus(int slotIndex) { + mSlotIndex = slotIndex; + } + + /** + * Get number of subscription slots. + * @return number of slots + */ + public int getSimSlotSize() { + return isAvailable() ? mTelephonyManager.getPhoneCount() : 0; + } + + /** + * Get the index of slot for this subscription. + * @return index of slot + */ + public int getSimSlotIndex() { + return mSlotIndex; + } + @Override public boolean isAvailable() { return SubscriptionUtil.isSimHardwareVisible(mContext) && @@ -72,22 +101,23 @@ public class SimStatusPreferenceController extends if (!SubscriptionUtil.isSimHardwareVisible(mContext)) { return; } - final Preference preference = screen.findPreference(getPreferenceKey()); + final Preference preference = screen.findPreference(KEY_SIM_STATUS); if (!isAvailable() || preference == null || !preference.isVisible()) { return; } final PreferenceCategory category = screen.findPreference(KEY_PREFERENCE_CATEGORY); - mPreferenceList.add(preference); - final int simStatusOrder = preference.getOrder(); + screen.removePreference(preference); + preference.setVisible(false); + // Add additional preferences for each sim in the device - for (int simSlotNumber = 1; simSlotNumber < mTelephonyManager.getPhoneCount(); + for (int simSlotNumber = 0; simSlotNumber < mTelephonyManager.getPhoneCount(); simSlotNumber++) { final Preference multiSimPreference = createNewPreference(screen.getContext()); multiSimPreference.setCopyingEnabled(true); - multiSimPreference.setOrder(simStatusOrder + simSlotNumber); - multiSimPreference.setKey(KEY_SIM_STATUS + simSlotNumber); + multiSimPreference.setOrder(simStatusOrder + simSlotNumber + 1); + multiSimPreference.setKey(KEY_SIM_STATUS + simSlotNumber + 1); category.addPreference(multiSimPreference); mPreferenceList.add(multiSimPreference); } diff --git a/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceControllerTest.java index 4ffaf7ae6ec..458f7ff296d 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceControllerTest.java @@ -24,8 +24,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.os.UserManager; import android.content.res.Resources; +import android.os.UserManager; import android.telephony.TelephonyManager; import androidx.fragment.app.Fragment; @@ -36,6 +36,9 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; +import java.util.ArrayList; +import java.util.List; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,7 +47,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.util.ReflectionHelpers; @RunWith(RobolectricTestRunner.class) public class SimStatusPreferenceControllerTest { @@ -52,6 +54,8 @@ public class SimStatusPreferenceControllerTest { @Mock private Preference mPreference; @Mock + private Preference mFirstSimPreference; + @Mock private Preference mSecondSimPreference; @Mock private PreferenceScreen mScreen; @@ -77,18 +81,31 @@ public class SimStatusPreferenceControllerTest { when(mContext.getResources()).thenReturn(mResources); when(mResources.getBoolean(R.bool.config_show_sim_info)).thenReturn(true); + doReturn(mTelephonyManager).when(mContext) + .getSystemService(Context.TELEPHONY_SERVICE); + doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); - mController = spy(new SimStatusPreferenceController(mContext, mFragment)); + final List preferencePool = new ArrayList(); + preferencePool.add(mFirstSimPreference); + preferencePool.add(mSecondSimPreference); + + mController = spy(new SimStatusPreferenceController(mContext, mFragment) { + @Override + public Preference createNewPreference(Context context) { + return preferencePool.remove(0); + } + }); doReturn(true).when(mController).isAvailable(); when(mScreen.getContext()).thenReturn(mContext); final String categoryKey = "device_detail_category"; when(mScreen.findPreference(categoryKey)).thenReturn(mCategory); - doReturn(mSecondSimPreference).when(mController).createNewPreference(mContext); - ReflectionHelpers.setField(mController, "mTelephonyManager", mTelephonyManager); - when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + final String baseEntryKey = "sim_status"; + when(mScreen.findPreference(baseEntryKey)).thenReturn(mPreference); final String prefKey = mController.getPreferenceKey(); when(mPreference.getKey()).thenReturn(prefKey); when(mPreference.isVisible()).thenReturn(true); + + mController.setSimSlotStatus(-1); } @Test @@ -107,8 +124,8 @@ public class SimStatusPreferenceControllerTest { mController.updateState(mPreference); - verify(mPreference).setTitle(mContext.getString(R.string.sim_status_title)); - verify(mPreference).setSummary(anyString()); + verify(mFirstSimPreference).setTitle(mContext.getString(R.string.sim_status_title)); + verify(mFirstSimPreference).setSummary(anyString()); } @Test @@ -118,11 +135,11 @@ public class SimStatusPreferenceControllerTest { mController.updateState(mPreference); - verify(mPreference).setTitle( + verify(mFirstSimPreference).setTitle( mContext.getString(R.string.sim_status_title_sim_slot, 1 /* sim slot */)); verify(mSecondSimPreference).setTitle( mContext.getString(R.string.sim_status_title_sim_slot, 2 /* sim slot */)); - verify(mPreference).setSummary(anyString()); + verify(mFirstSimPreference).setSummary(anyString()); verify(mSecondSimPreference).setSummary(anyString()); } @@ -130,9 +147,10 @@ public class SimStatusPreferenceControllerTest { public void handlePreferenceTreeClick_shouldStartDialogFragment() { when(mFragment.getChildFragmentManager()).thenReturn( mock(FragmentManager.class, Answers.RETURNS_DEEP_STUBS)); + when(mTelephonyManager.getPhoneCount()).thenReturn(2); mController.displayPreference(mScreen); - mController.handlePreferenceTreeClick(mPreference); + mController.handlePreferenceTreeClick(mFirstSimPreference); verify(mFragment).getChildFragmentManager(); } From 33beb69e332410bc123f3e1eb7d55346d3527441 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Wed, 30 Nov 2022 15:55:16 +0800 Subject: [PATCH 08/10] Clean up AllAppListModel.filter Since it is same as default. Bug: 260660819 Test: Make Settings Change-Id: I7eab8843f44a3457805512bab0d8cb02e9427ca4 --- src/com/android/settings/spa/app/AllAppList.kt | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/com/android/settings/spa/app/AllAppList.kt b/src/com/android/settings/spa/app/AllAppList.kt index f8c5c0f63e9..75b5cd21963 100644 --- a/src/com/android/settings/spa/app/AllAppList.kt +++ b/src/com/android/settings/spa/app/AllAppList.kt @@ -27,7 +27,7 @@ import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.compose.navigator -import com.android.settingslib.spa.framework.util.asyncMapItem +import com.android.settingslib.spa.framework.util.mapItem import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spaprivileged.model.app.AppListModel @@ -78,15 +78,7 @@ data class AppRecordWithSize( private class AllAppListModel : AppListModel { override fun transform(userIdFlow: Flow, appListFlow: Flow>) = - appListFlow.asyncMapItem { app -> - AppRecordWithSize(app) - } - - override fun filter( - userIdFlow: Flow, - option: Int, - recordListFlow: Flow>, - ) = recordListFlow + appListFlow.mapItem(::AppRecordWithSize) @Composable override fun getSummary(option: Int, record: AppRecordWithSize) = record.app.getStorageSize() From 362def5275a4528c375ae366ead47bef6b34a0fc Mon Sep 17 00:00:00 2001 From: Kuan Wang Date: Thu, 1 Dec 2022 12:04:12 +0800 Subject: [PATCH 09/10] Fix the issue that no data is returned when there is no full charge data in the database. Test: make RunSettingsRoboTests + manual Bug: 260938983 Bug: 253553141 Change-Id: Ie12f239ce83a6ac2cd95c1663cd82e2974a7eaf3 --- .../batteryusage/db/BatteryStateDao.java | 4 ++-- .../batteryusage/db/BatteryStateDaoTest.java | 24 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java index 936d5e053c7..6d2ab8d3c26 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java +++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java @@ -43,8 +43,8 @@ public interface BatteryStateDao { /** Gets the {@link Cursor} of all recorded data since last full charge within 7 days. */ @Query("SELECT * FROM BatteryState WHERE timestamp >= :timestampSixDaysAgo AND timestamp >= " - + "(SELECT MAX(timestamp) FROM BatteryState WHERE isFullChargeCycleStart = 1)" - + " ORDER BY timestamp ASC") + + "(SELECT IFNULL((SELECT MAX(timestamp) FROM BatteryState " + + "WHERE isFullChargeCycleStart = 1), 0)) ORDER BY timestamp ASC") Cursor getCursorSinceLastFullCharge(long timestampSixDaysAgo); /** Get the count of distinct timestamp after a specific timestamp. */ diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java index 33735b31e9f..3b887addb40 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java @@ -53,10 +53,8 @@ public final class BatteryStateDaoTest { mContext = ApplicationProvider.getApplicationContext(); mDatabase = BatteryTestUtils.setUpBatteryStateDatabase(mContext); mBatteryStateDao = mDatabase.batteryStateDao(); - BatteryTestUtils.insertDataToBatteryStateDatabase( - mContext, TIMESTAMP3, PACKAGE_NAME3); - BatteryTestUtils.insertDataToBatteryStateDatabase( - mContext, TIMESTAMP2, PACKAGE_NAME2); + BatteryTestUtils.insertDataToBatteryStateDatabase(mContext, TIMESTAMP3, PACKAGE_NAME3); + BatteryTestUtils.insertDataToBatteryStateDatabase(mContext, TIMESTAMP2, PACKAGE_NAME2); BatteryTestUtils.insertDataToBatteryStateDatabase( mContext, TIMESTAMP1, PACKAGE_NAME1, /*multiple=*/ true, /*isFullChargeStart=*/ true); @@ -100,6 +98,24 @@ public final class BatteryStateDaoTest { assertThat(cursor2.getString(3 /*packageName*/)).isEqualTo(PACKAGE_NAME3); } + @Test + public void batteryStateDao_getCursorSinceLastFullCharge_noFullChargeData_returnSevenDaysData() + throws Exception { + mBatteryStateDao.clearAll(); + BatteryTestUtils.insertDataToBatteryStateDatabase(mContext, TIMESTAMP3, PACKAGE_NAME3); + BatteryTestUtils.insertDataToBatteryStateDatabase(mContext, TIMESTAMP2, PACKAGE_NAME2); + BatteryTestUtils.insertDataToBatteryStateDatabase(mContext, TIMESTAMP1, PACKAGE_NAME1); + final Cursor cursor = mBatteryStateDao.getCursorSinceLastFullCharge(TIMESTAMP2); + assertThat(cursor.getCount()).isEqualTo(2); + assertThat(cursor.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE); + // Verifies the queried first battery state. + cursor.moveToFirst(); + assertThat(cursor.getString(3 /*packageName*/)).isEqualTo(PACKAGE_NAME2); + // Verifies the queried third battery state. + cursor.moveToNext(); + assertThat(cursor.getString(3 /*packageName*/)).isEqualTo(PACKAGE_NAME3); + } + @Test public void batteryStateDao_clearAllBefore() throws Exception { mBatteryStateDao.clearAllBefore(TIMESTAMP2); From b19aeb63b6c0ffdba9fc5193f64c52d30c474897 Mon Sep 17 00:00:00 2001 From: lbill Date: Wed, 30 Nov 2022 02:47:43 +0000 Subject: [PATCH 10/10] Fix flaky fingerprintUdfpsOverlayEnrollment_PlaysAllAnimationsAssetsCorrectly We should return the corresponding getStageThresholdSteps() for udfps test, otherwise the test can simulate last stage only and make the test fail. Bug: 228100413 Bug: 260659253 Test: make RunSettingsRoboTests ROBOTEST_FILTER= \ "com.android.settings.biometrics.fingerprint .FingerprintEnrollEnrollingTest" Change-Id: I4c712c84f357f275e5a169d16d2ed382fa4494ef --- .../FingerprintEnrollEnrollingTest.java | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java index 185af9e7213..f9d4445a658 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java @@ -91,7 +91,10 @@ public class FingerprintEnrollEnrollingTest { private Resources.Theme mTheme; + private static final int TOTAL_ENROLL_STEPS = 25; + private final int[] mSfpsStageThresholds = new int[]{0, 9, 13, 19, 25}; + private final int[] mUdfpsStageThresholds = new int[]{0, 13, 17, 22}; private FingerprintEnrollEnrolling mActivity; private Context mContext; @@ -190,19 +193,19 @@ public class FingerprintEnrollEnrollingTest { public void fingerprintUdfpsOverlayEnrollment_PlaysAllAnimationsAssetsCorrectly() { initializeActivityFor(TYPE_UDFPS_OPTICAL); - int totalEnrollSteps = 25; int initStageSteps = -1, initStageRemaining = 0; + final int totalStages = mUdfpsStageThresholds.length; when(mSidecar.getEnrollmentSteps()).thenReturn(initStageSteps); when(mSidecar.getEnrollmentRemaining()).thenReturn(initStageRemaining); mActivity.onEnrollmentProgressChange(initStageSteps, initStageRemaining); - when(mSidecar.getEnrollmentSteps()).thenReturn(totalEnrollSteps); + when(mSidecar.getEnrollmentSteps()).thenReturn(TOTAL_ENROLL_STEPS); - for (int remaining = totalEnrollSteps; remaining > 0; remaining--) { + for (int remaining = TOTAL_ENROLL_STEPS; remaining > 0; remaining--) { when(mSidecar.getEnrollmentRemaining()).thenReturn(remaining); - mActivity.onEnrollmentProgressChange(totalEnrollSteps, remaining); + mActivity.onEnrollmentProgressChange(TOTAL_ENROLL_STEPS, remaining); } List expectedLottieAssetOrder = List.of( @@ -213,7 +216,7 @@ public class FingerprintEnrollEnrollingTest { ); ArgumentCaptor lottieAssetCaptor = ArgumentCaptor.forClass(Integer.class); - verify(mIllustrationLottie, times(4)).setAnimation(lottieAssetCaptor.capture()); + verify(mIllustrationLottie, times(totalStages)).setAnimation(lottieAssetCaptor.capture()); List observedLottieAssetOrder = lottieAssetCaptor.getAllValues(); assertThat(observedLottieAssetOrder).isEqualTo(expectedLottieAssetOrder); } @@ -222,7 +225,6 @@ public class FingerprintEnrollEnrollingTest { public void fingerprintSfpsEnroll_PlaysAllAnimationsAssetsCorrectly() { initializeActivityFor(TYPE_POWER_BUTTON); - int totalEnrollSteps = 25; int initStageSteps = -1, initStageRemaining = 0; when(mSidecar.getEnrollmentSteps()).thenReturn(initStageSteps); @@ -230,11 +232,11 @@ public class FingerprintEnrollEnrollingTest { mActivity.onEnrollmentProgressChange(initStageSteps, initStageRemaining); - when(mSidecar.getEnrollmentSteps()).thenReturn(totalEnrollSteps); + when(mSidecar.getEnrollmentSteps()).thenReturn(TOTAL_ENROLL_STEPS); - for (int remaining = totalEnrollSteps; remaining > 0; remaining--) { + for (int remaining = TOTAL_ENROLL_STEPS; remaining > 0; remaining--) { when(mSidecar.getEnrollmentRemaining()).thenReturn(remaining); - mActivity.onEnrollmentProgressChange(totalEnrollSteps, remaining); + mActivity.onEnrollmentProgressChange(TOTAL_ENROLL_STEPS, remaining); } List expectedLottieAssetOrder = List.of( @@ -360,6 +362,16 @@ public class FingerprintEnrollEnrollingTest { doReturn(true).when(mSidecar).isEnrolling(); } + if (sensorType == TYPE_UDFPS_OPTICAL) { + // UDFPS : STAGE_CENTER = 0, ... , STAGE_RIGHT_EDGE = 3 + final int totalStages = mUdfpsStageThresholds.length - 1; + for (int stage = 0; stage <= totalStages; stage++) { + doReturn(mUdfpsStageThresholds[stage]).when(mActivity).getStageThresholdSteps( + stage); + } + doReturn(true).when(mSidecar).isEnrolling(); + } + ActivityController.of(mActivity).create(savedInstanceState); mTheme = mActivity.getTheme(); }