Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/split/MagneticDividerUtils.kt +127 −131 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import androidx.compose.ui.unit.dp import com.android.mechanics.spec.Mapping import com.android.mechanics.spec.MotionSpec import com.android.mechanics.spec.SemanticKey import com.android.mechanics.spec.builder.MotionBuilderContext import com.android.mechanics.spec.builder.spatialDirectionalMotionSpec import com.android.mechanics.spec.with import com.android.mechanics.spring.SpringParameters Loading @@ -31,11 +32,10 @@ import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget * Utility class used to create a framework that enables the divider to snap magnetically to snap * points while the user is dragging it. */ class MagneticDividerUtils { companion object { object MagneticDividerUtils { /** * When the user moves the divider towards or away from a snap point, a magnetic spring * movement and haptic will take place at this distance. * When the user moves the divider towards or away from a snap point, a magnetic spring movement * and haptic will take place at this distance. */ private val DEFAULT_MAGNETIC_ATTACH_THRESHOLD = 56.dp /** The minimum spacing between snap zones, to prevent overlap on smaller displays. */ Loading @@ -45,90 +45,88 @@ class MagneticDividerUtils { /** The damping ratio of the magnetic snap effect. */ private const val ATTACH_DAMPING_RATIO = 0.95f /** The spring used for the magnetic snap effect. */ private val MagneticSpring = SpringParameters( stiffness = ATTACH_STIFFNESS, dampingRatio = ATTACH_DAMPING_RATIO ) /** * When inside the magnetic snap zone, the divider's movement is reduced by this amount. */ private val MagneticSpring = SpringParameters(stiffness = ATTACH_STIFFNESS, dampingRatio = ATTACH_DAMPING_RATIO) /** When inside the magnetic snap zone, the divider's movement is reduced by this amount. */ private const val ATTACH_DETACH_SCALE = 0.5f /** * A key that can be passed into a MotionValue to retrieve the SnapPosition associated * with the current drag. * A key that can be passed into a MotionValue to retrieve the SnapPosition associated with the * current drag. */ @JvmStatic val SNAP_POSITION_KEY = SemanticKey<Int?>() /** * Key used for identity regions which don't have drop zones associated with them. * Need to keep this key separate for the SemanticKeys we create with null values as it * seems like this overwrites the semantics created with real snapTarget values * Key used for identity regions which don't have drop zones associated with them. Need to keep * this key separate for the SemanticKeys we create with null values as it seems like this * overwrites the semantics created with real snapTarget values */ @JvmStatic private val SNAP_POSITION_KEY_IDENTITY = SemanticKey<Int?>() /** * Create a MotionSpec that has "snap zones" for each of the SnapTargets provided. * * NOTE: This exists for Java/View interoperability only */ @JvmStatic fun generateMotionSpec(targets: List<SnapTarget>, res: Resources): MotionSpec { // Create a new MotionSpec object and return it. A MotionSpec is composed of at least // one DirectionalMotionSpec, so below we will create a DirectionalMotionSpec and pass // it as the single argument to the MotionSpec constructor. return MotionSpec( // To do that, we first create a "context" object, which gives access to a // DirectionalMotionSpec builder and some convenience functions, like for converting // dp > px. with(standardViewMotionBuilderContext(res.displayMetrics.density)) { // Inside of this "with" block, we can write code freely -- the final evaluated // value of this block will be the value of the final expression we write. See // Kotlin docs for more details. fun generateMotionSpec(targets: List<SnapTarget>, resources: Resources): MotionSpec { return with(standardViewMotionBuilderContext(resources.displayMetrics.density)) { generateMotionSpec(targets) } } /** Create a MotionSpec that has "snap zones" for each of the SnapTargets provided. */ fun MotionBuilderContext.generateMotionSpec(targets: List<SnapTarget>): MotionSpec { // First, get the position of the left-most (or top-most) dismiss point. val topLeftDismissTarget = targets.first() val topLeftDismissPosition = topLeftDismissTarget.position.toFloat() return MotionSpec( // Create a DirectionalMotionSpec using a pre-set builder method. We choose the // "spatialDirectionalMotionSpec", which is meant for "spatial" movement (as // opposed to "effects" movement). // "spatialDirectionalMotionSpec", which is meant for "spatial" movement (as opposed to // "effects" movement). spatialDirectionalMotionSpec( initialMapping = Mapping.Fixed(topLeftDismissPosition), semantics = listOf(SNAP_POSITION_KEY_IDENTITY with null), defaultSpring = MagneticSpring defaultSpring = MagneticSpring, ) { // NOTE: This block is a trailing lambda passed in as the "init" parameter. // A DirectionalMotionSpec is essentially a number line from -infinity to // infinity, with instructions on how to interpret the value at each point. // We create each individual segment below to fill out our number line. // A DirectionalMotionSpec is essentially a number line from -infinity to infinity, // with instructions on how to interpret the value at each point. We create each // individual segment below to fill out our number line. // Start by finding the smallest span between two targets and setting an // appropriate magnetic snap threshold. val smallestSpanBetweenTargets = targets.zipWithNext { t1, t2 -> t2.position.toFloat() - t1.position.toFloat() }.reduce { minSoFar, currentDiff -> kotlin.math.min(minSoFar, currentDiff) } val availableSpaceForSnapZone = (smallestSpanBetweenTargets - MINIMUM_SPACE_BETWEEN_SNAP_ZONES.toPx()) / 2f val snapThreshold = kotlin.math.min( DEFAULT_MAGNETIC_ATTACH_THRESHOLD.toPx(), availableSpaceForSnapZone) // Our first breakpoint is located at topLeftDismissPosition. On the right // side of this breakpoint, we'll use the "identity" instruction, which // means values won't be converted. // Start by finding the smallest span between two targets and setting an appropriate // magnetic snap threshold. val smallestSpanBetweenTargets = targets .zipWithNext { t1, t2 -> t2.position.toFloat() - t1.position.toFloat() } .reduce { minSoFar, currentDiff -> kotlin.math.min(minSoFar, currentDiff) } val availableSpaceForSnapZone = (smallestSpanBetweenTargets - MINIMUM_SPACE_BETWEEN_SNAP_ZONES.toPx()) / 2f val snapThreshold = kotlin.math.min( DEFAULT_MAGNETIC_ATTACH_THRESHOLD.toPx(), availableSpaceForSnapZone, ) // Our first breakpoint is located at topLeftDismissPosition. On the right side of // this breakpoint, we'll use the "identity" instruction, which means values won't // be converted. identity( breakpoint = topLeftDismissPosition, semantics = listOf(SNAP_POSITION_KEY with null) semantics = listOf(SNAP_POSITION_KEY with null), ) // We continue creating alternating zones of "identity" and // "fractionalInputFromCurrent", which will give us the behavior we're // looking for, where the divider can be dragged along normally in some // areas (the identity zones) and resists the user's movement in some areas // (the fractionalInputFromCurrent zones). The targets have to be created in // ascending order. // Iterating from the second target to the second-last target (EXCLUDING the // first and last): // "fractionalInputFromCurrent", which will give us the behavior we're looking for, // where the divider can be dragged along normally in some areas (the identity // zones) and resists the user's movement in some areas (the // fractionalInputFromCurrent zones). The targets have to be created in ascending // order. // Iterating from the second target to the second-last target (EXCLUDING the first // and last): for (i in (1 until targets.size - 1)) { val target = targets[i] val targetPosition = target.position.toFloat() Loading @@ -136,9 +134,9 @@ class MagneticDividerUtils { // Create a fractionalInputFromCurrent zone. fractionalInputFromCurrent( breakpoint = targetPosition - snapThreshold, // With every magnetic segment, we also pass in the associated // snapPosition as a "semantic association", so we can later query // the MotionValue for it. // With every magnetic segment, we also pass in the associated snapPosition // as a "semantic association", so we can later query the MotionValue for // it. semantics = listOf(SNAP_POSITION_KEY with target.snapPosition), delta = snapThreshold * (1 - ATTACH_DETACH_SCALE), fraction = ATTACH_DETACH_SCALE, Loading @@ -147,22 +145,20 @@ class MagneticDividerUtils { // Create another identity zone. identity( breakpoint = targetPosition + snapThreshold, semantics = listOf(SNAP_POSITION_KEY_IDENTITY with null) semantics = listOf(SNAP_POSITION_KEY_IDENTITY with null), ) } // Finally, create one last fixedValue zone, from the bottom/right // dismiss point to infinity. // Finally, create one last fixedValue zone, from the bottom/right dismiss point to // infinity. val bottomRightDismissTarget = targets.last() val bottomRightDismissPosition = bottomRightDismissTarget.position.toFloat() fixedValue( breakpoint = bottomRightDismissPosition, value = bottomRightDismissPosition, semantics = listOf(SNAP_POSITION_KEY_IDENTITY with null) semantics = listOf(SNAP_POSITION_KEY_IDENTITY with null), ) } } ) } } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/MagneticDividerUtilsTests.java→libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/MagneticDividerUtilsTests.kt +56 −0 Original line number Diff line number Diff line Loading @@ -13,56 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.common.split package com.android.wm.shell.common.split; import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget import com.android.wm.shell.common.split.MagneticDividerUtils.generateMotionSpec import com.android.wm.shell.shared.split.SplitScreenConstants import com.google.common.truth.Truth.assertThat import kotlin.math.max import org.junit.Test import org.junit.runner.RunWith import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS; import android.content.res.Resources; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; import com.android.mechanics.spec.MotionSpec; import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.List; @RunWith(AndroidJUnit4.class) public class MagneticDividerUtilsTests { Resources mResources; @Before public void setup() { mResources = InstrumentationRegistry.getInstrumentation().getContext().getResources(); } @RunWith(AndroidJUnit4::class) class MagneticDividerUtilsTests { @Test public void generateMotionSpec_worksOnThisDeviceWithoutCrashing() { int longEdge = Math.max( mResources.getDisplayMetrics().heightPixels, mResources.getDisplayMetrics().widthPixels ); List<SnapTarget> mTargets = List.of( new SnapTarget(0, SNAP_TO_START_AND_DISMISS), new SnapTarget(longEdge / 10, SNAP_TO_2_10_90), new SnapTarget(longEdge / 2, SNAP_TO_2_50_50), new SnapTarget(longEdge - (longEdge / 10), SNAP_TO_2_90_10), new SnapTarget(longEdge, SNAP_TO_END_AND_DISMISS) ); fun generateMotionSpec_worksOnThisDeviceWithoutCrashing() { // Retrieve long edge and motion builder context (density) from this device. val resources = InstrumentationRegistry.getInstrumentation().context.resources val longEdge = max( resources.displayMetrics.heightPixels.toDouble(), resources.displayMetrics.widthPixels.toDouble(), ) .toInt() val targets = listOf( SnapTarget(0, SplitScreenConstants.SNAP_TO_START_AND_DISMISS), SnapTarget(longEdge / 10, SplitScreenConstants.SNAP_TO_2_10_90), SnapTarget(longEdge / 2, SplitScreenConstants.SNAP_TO_2_50_50), SnapTarget(longEdge - (longEdge / 10), SplitScreenConstants.SNAP_TO_2_90_10), SnapTarget(longEdge, SplitScreenConstants.SNAP_TO_END_AND_DISMISS), ) // Check that a MotionSpec gets created without crashing. A crash can happen if the dp // values set MagneticDividerUtils are large enough that the snap zones overlap on smaller // screens. MotionSpec motionSpec = MagneticDividerUtils.generateMotionSpec(mTargets, mResources); assertThat(generateMotionSpec(targets, resources)).isNotNull() } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/split/MagneticDividerUtils.kt +127 −131 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import androidx.compose.ui.unit.dp import com.android.mechanics.spec.Mapping import com.android.mechanics.spec.MotionSpec import com.android.mechanics.spec.SemanticKey import com.android.mechanics.spec.builder.MotionBuilderContext import com.android.mechanics.spec.builder.spatialDirectionalMotionSpec import com.android.mechanics.spec.with import com.android.mechanics.spring.SpringParameters Loading @@ -31,11 +32,10 @@ import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget * Utility class used to create a framework that enables the divider to snap magnetically to snap * points while the user is dragging it. */ class MagneticDividerUtils { companion object { object MagneticDividerUtils { /** * When the user moves the divider towards or away from a snap point, a magnetic spring * movement and haptic will take place at this distance. * When the user moves the divider towards or away from a snap point, a magnetic spring movement * and haptic will take place at this distance. */ private val DEFAULT_MAGNETIC_ATTACH_THRESHOLD = 56.dp /** The minimum spacing between snap zones, to prevent overlap on smaller displays. */ Loading @@ -45,90 +45,88 @@ class MagneticDividerUtils { /** The damping ratio of the magnetic snap effect. */ private const val ATTACH_DAMPING_RATIO = 0.95f /** The spring used for the magnetic snap effect. */ private val MagneticSpring = SpringParameters( stiffness = ATTACH_STIFFNESS, dampingRatio = ATTACH_DAMPING_RATIO ) /** * When inside the magnetic snap zone, the divider's movement is reduced by this amount. */ private val MagneticSpring = SpringParameters(stiffness = ATTACH_STIFFNESS, dampingRatio = ATTACH_DAMPING_RATIO) /** When inside the magnetic snap zone, the divider's movement is reduced by this amount. */ private const val ATTACH_DETACH_SCALE = 0.5f /** * A key that can be passed into a MotionValue to retrieve the SnapPosition associated * with the current drag. * A key that can be passed into a MotionValue to retrieve the SnapPosition associated with the * current drag. */ @JvmStatic val SNAP_POSITION_KEY = SemanticKey<Int?>() /** * Key used for identity regions which don't have drop zones associated with them. * Need to keep this key separate for the SemanticKeys we create with null values as it * seems like this overwrites the semantics created with real snapTarget values * Key used for identity regions which don't have drop zones associated with them. Need to keep * this key separate for the SemanticKeys we create with null values as it seems like this * overwrites the semantics created with real snapTarget values */ @JvmStatic private val SNAP_POSITION_KEY_IDENTITY = SemanticKey<Int?>() /** * Create a MotionSpec that has "snap zones" for each of the SnapTargets provided. * * NOTE: This exists for Java/View interoperability only */ @JvmStatic fun generateMotionSpec(targets: List<SnapTarget>, res: Resources): MotionSpec { // Create a new MotionSpec object and return it. A MotionSpec is composed of at least // one DirectionalMotionSpec, so below we will create a DirectionalMotionSpec and pass // it as the single argument to the MotionSpec constructor. return MotionSpec( // To do that, we first create a "context" object, which gives access to a // DirectionalMotionSpec builder and some convenience functions, like for converting // dp > px. with(standardViewMotionBuilderContext(res.displayMetrics.density)) { // Inside of this "with" block, we can write code freely -- the final evaluated // value of this block will be the value of the final expression we write. See // Kotlin docs for more details. fun generateMotionSpec(targets: List<SnapTarget>, resources: Resources): MotionSpec { return with(standardViewMotionBuilderContext(resources.displayMetrics.density)) { generateMotionSpec(targets) } } /** Create a MotionSpec that has "snap zones" for each of the SnapTargets provided. */ fun MotionBuilderContext.generateMotionSpec(targets: List<SnapTarget>): MotionSpec { // First, get the position of the left-most (or top-most) dismiss point. val topLeftDismissTarget = targets.first() val topLeftDismissPosition = topLeftDismissTarget.position.toFloat() return MotionSpec( // Create a DirectionalMotionSpec using a pre-set builder method. We choose the // "spatialDirectionalMotionSpec", which is meant for "spatial" movement (as // opposed to "effects" movement). // "spatialDirectionalMotionSpec", which is meant for "spatial" movement (as opposed to // "effects" movement). spatialDirectionalMotionSpec( initialMapping = Mapping.Fixed(topLeftDismissPosition), semantics = listOf(SNAP_POSITION_KEY_IDENTITY with null), defaultSpring = MagneticSpring defaultSpring = MagneticSpring, ) { // NOTE: This block is a trailing lambda passed in as the "init" parameter. // A DirectionalMotionSpec is essentially a number line from -infinity to // infinity, with instructions on how to interpret the value at each point. // We create each individual segment below to fill out our number line. // A DirectionalMotionSpec is essentially a number line from -infinity to infinity, // with instructions on how to interpret the value at each point. We create each // individual segment below to fill out our number line. // Start by finding the smallest span between two targets and setting an // appropriate magnetic snap threshold. val smallestSpanBetweenTargets = targets.zipWithNext { t1, t2 -> t2.position.toFloat() - t1.position.toFloat() }.reduce { minSoFar, currentDiff -> kotlin.math.min(minSoFar, currentDiff) } val availableSpaceForSnapZone = (smallestSpanBetweenTargets - MINIMUM_SPACE_BETWEEN_SNAP_ZONES.toPx()) / 2f val snapThreshold = kotlin.math.min( DEFAULT_MAGNETIC_ATTACH_THRESHOLD.toPx(), availableSpaceForSnapZone) // Our first breakpoint is located at topLeftDismissPosition. On the right // side of this breakpoint, we'll use the "identity" instruction, which // means values won't be converted. // Start by finding the smallest span between two targets and setting an appropriate // magnetic snap threshold. val smallestSpanBetweenTargets = targets .zipWithNext { t1, t2 -> t2.position.toFloat() - t1.position.toFloat() } .reduce { minSoFar, currentDiff -> kotlin.math.min(minSoFar, currentDiff) } val availableSpaceForSnapZone = (smallestSpanBetweenTargets - MINIMUM_SPACE_BETWEEN_SNAP_ZONES.toPx()) / 2f val snapThreshold = kotlin.math.min( DEFAULT_MAGNETIC_ATTACH_THRESHOLD.toPx(), availableSpaceForSnapZone, ) // Our first breakpoint is located at topLeftDismissPosition. On the right side of // this breakpoint, we'll use the "identity" instruction, which means values won't // be converted. identity( breakpoint = topLeftDismissPosition, semantics = listOf(SNAP_POSITION_KEY with null) semantics = listOf(SNAP_POSITION_KEY with null), ) // We continue creating alternating zones of "identity" and // "fractionalInputFromCurrent", which will give us the behavior we're // looking for, where the divider can be dragged along normally in some // areas (the identity zones) and resists the user's movement in some areas // (the fractionalInputFromCurrent zones). The targets have to be created in // ascending order. // Iterating from the second target to the second-last target (EXCLUDING the // first and last): // "fractionalInputFromCurrent", which will give us the behavior we're looking for, // where the divider can be dragged along normally in some areas (the identity // zones) and resists the user's movement in some areas (the // fractionalInputFromCurrent zones). The targets have to be created in ascending // order. // Iterating from the second target to the second-last target (EXCLUDING the first // and last): for (i in (1 until targets.size - 1)) { val target = targets[i] val targetPosition = target.position.toFloat() Loading @@ -136,9 +134,9 @@ class MagneticDividerUtils { // Create a fractionalInputFromCurrent zone. fractionalInputFromCurrent( breakpoint = targetPosition - snapThreshold, // With every magnetic segment, we also pass in the associated // snapPosition as a "semantic association", so we can later query // the MotionValue for it. // With every magnetic segment, we also pass in the associated snapPosition // as a "semantic association", so we can later query the MotionValue for // it. semantics = listOf(SNAP_POSITION_KEY with target.snapPosition), delta = snapThreshold * (1 - ATTACH_DETACH_SCALE), fraction = ATTACH_DETACH_SCALE, Loading @@ -147,22 +145,20 @@ class MagneticDividerUtils { // Create another identity zone. identity( breakpoint = targetPosition + snapThreshold, semantics = listOf(SNAP_POSITION_KEY_IDENTITY with null) semantics = listOf(SNAP_POSITION_KEY_IDENTITY with null), ) } // Finally, create one last fixedValue zone, from the bottom/right // dismiss point to infinity. // Finally, create one last fixedValue zone, from the bottom/right dismiss point to // infinity. val bottomRightDismissTarget = targets.last() val bottomRightDismissPosition = bottomRightDismissTarget.position.toFloat() fixedValue( breakpoint = bottomRightDismissPosition, value = bottomRightDismissPosition, semantics = listOf(SNAP_POSITION_KEY_IDENTITY with null) semantics = listOf(SNAP_POSITION_KEY_IDENTITY with null), ) } } ) } } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/MagneticDividerUtilsTests.java→libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/MagneticDividerUtilsTests.kt +56 −0 Original line number Diff line number Diff line Loading @@ -13,56 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.common.split package com.android.wm.shell.common.split; import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget import com.android.wm.shell.common.split.MagneticDividerUtils.generateMotionSpec import com.android.wm.shell.shared.split.SplitScreenConstants import com.google.common.truth.Truth.assertThat import kotlin.math.max import org.junit.Test import org.junit.runner.RunWith import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS; import android.content.res.Resources; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; import com.android.mechanics.spec.MotionSpec; import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.List; @RunWith(AndroidJUnit4.class) public class MagneticDividerUtilsTests { Resources mResources; @Before public void setup() { mResources = InstrumentationRegistry.getInstrumentation().getContext().getResources(); } @RunWith(AndroidJUnit4::class) class MagneticDividerUtilsTests { @Test public void generateMotionSpec_worksOnThisDeviceWithoutCrashing() { int longEdge = Math.max( mResources.getDisplayMetrics().heightPixels, mResources.getDisplayMetrics().widthPixels ); List<SnapTarget> mTargets = List.of( new SnapTarget(0, SNAP_TO_START_AND_DISMISS), new SnapTarget(longEdge / 10, SNAP_TO_2_10_90), new SnapTarget(longEdge / 2, SNAP_TO_2_50_50), new SnapTarget(longEdge - (longEdge / 10), SNAP_TO_2_90_10), new SnapTarget(longEdge, SNAP_TO_END_AND_DISMISS) ); fun generateMotionSpec_worksOnThisDeviceWithoutCrashing() { // Retrieve long edge and motion builder context (density) from this device. val resources = InstrumentationRegistry.getInstrumentation().context.resources val longEdge = max( resources.displayMetrics.heightPixels.toDouble(), resources.displayMetrics.widthPixels.toDouble(), ) .toInt() val targets = listOf( SnapTarget(0, SplitScreenConstants.SNAP_TO_START_AND_DISMISS), SnapTarget(longEdge / 10, SplitScreenConstants.SNAP_TO_2_10_90), SnapTarget(longEdge / 2, SplitScreenConstants.SNAP_TO_2_50_50), SnapTarget(longEdge - (longEdge / 10), SplitScreenConstants.SNAP_TO_2_90_10), SnapTarget(longEdge, SplitScreenConstants.SNAP_TO_END_AND_DISMISS), ) // Check that a MotionSpec gets created without crashing. A crash can happen if the dp // values set MagneticDividerUtils are large enough that the snap zones overlap on smaller // screens. MotionSpec motionSpec = MagneticDividerUtils.generateMotionSpec(mTargets, mResources); assertThat(generateMotionSpec(targets, resources)).isNotNull() } }