# Scaling input interactions beyond the touchscreen with Jetpack Compose - Speakers: Alex Vanyo (Software Engineer), Jeremy Walker (Compose Software Engineer) - Track: Apps foundation - Video: https://www.youtube.com/watch?v=iztM_PgoCjw - Page: https://developer.android.com/events/dev-summit/talks/scaling-input-interactions-beyond-the-touchscreen-with-jetpack-compose ## About the talk As new form factors move beyond the touchscreen, Compose's input system has evolved to meet the challenge. Its layered architecture provides the foundation for supporting everything from advanced trackpad gestures to novel hardware inputs seamlessly. Through clear architectural breakdowns and hands-on code examples, follow the lifecycle of an input event all the way from the OS platform layer up to Compose gesture detectors and UI components. You will discover how to trace events across the entire stack, apply key architectural patterns for emerging input types, and confidently debug complex gesture interactions in your own apps. ## Summary - Jetpack Compose processes incoming Android pointer events through a six-phase pipeline. This pipeline maintains a hit path tree and dispatches events in three passes across UI nodes. - Trackpad inputs in Compose now map to mouse events instead of touch. Compose also converts platform fake finger events into dedicated pan and scale pointer events. - Intelligent eyewear trackpads introduce indirect input types rather than screen coordinate touches. They use a Primary Directional Motion Axis to map horizontal swipes into vertical next or previous navigation. - Testing support has expanded beyond touch with new methods such as `performTrackpadInput`. These APIs provide familiar testing surfaces to validate that apps handle non-touch devices correctly. ## Topics jetpack-compose, pointer-input, trackpads, gestures, intelligent-eyewear, accessibility ## Transcript ### Introduction and input philosophy [00:05](https://www.youtube.com/watch?v=iztM_PgoCjw&t=5s) Hi, everyone! [00:05](https://www.youtube.com/watch?v=iztM_PgoCjw&t=5s) Hope you're having a good time at the Android Developer Summit, and thanks for attending our talk. [00:10](https://www.youtube.com/watch?v=iztM_PgoCjw&t=10s) I'm Alex Vanyo, a software engineer on the Compose team, and I'm joined by Jeremy, also a software engineer on the Compose team. [00:16](https://www.youtube.com/watch?v=iztM_PgoCjw&t=16s) Both of us have been working to enhance Jetpack Compose's input system, and today, we're going to talk about how we've scaled them beyond the touchscreen to make it easier to support non-touch input out of the box. [00:29](https://www.youtube.com/watch?v=iztM_PgoCjw&t=29s) Input, broadly, captures any way that a user interacts with your app. [00:34](https://www.youtube.com/watch?v=iztM_PgoCjw&t=34s) Touchscreens are probably the first thing that you think about for a mobile app, but they're definitely not the only type of input. [00:39](https://www.youtube.com/watch?v=iztM_PgoCjw&t=39s) Other types of input include physical keyboards, mice, trackpads, styluses, controllers, and rotary devices. [00:47](https://www.youtube.com/watch?v=iztM_PgoCjw&t=47s) And we're also seeing new novel input types as well, such as trackpads on intelligent eyewear, eye and hand tracking in XR, and finger and wrist gestures on Wear. [00:57](https://www.youtube.com/watch?v=iztM_PgoCjw&t=57s) And if you want to go even more general, there's other types of input, as well: things like software IMEs for text editing, microphones, cameras, and other device sensors. [01:10](https://www.youtube.com/watch?v=iztM_PgoCjw&t=70s) You need to expect that your apps will be used with a wide variety of input types. [01:16](https://www.youtube.com/watch?v=iztM_PgoCjw&t=76s) One reason for this is the ever-increasing variety of devices that applications are running on, and the increasing adaptability of those devices. [01:25](https://www.youtube.com/watch?v=iztM_PgoCjw&t=85s) Device form factors may have a most common input method, but it's important to not assume that a particular device form factor implies the usage of a particular input type. [01:37](https://www.youtube.com/watch?v=iztM_PgoCjw&t=97s) While a Chromebook has a built-in trackpad and keyboard, that doesn't make those input types unique to that form factor. [01:44](https://www.youtube.com/watch?v=iztM_PgoCjw&t=104s) You should expect physical keyboards, trackpads, and mice to be used on phones, tablets, and TVs, as well, and some TVs even have touchscreens. [01:54](https://www.youtube.com/watch?v=iztM_PgoCjw&t=114s) A second big reason to expect and support multiple input types is accessibility. [02:00](https://www.youtube.com/watch?v=iztM_PgoCjw&t=120s) Allowing users to use their input device of choice and allowing them to switch input devices as they see fit is the right thing to do to make applications more accessible and usable. [02:13](https://www.youtube.com/watch?v=iztM_PgoCjw&t=133s) And depending on jurisdiction, it may be a legal requirement to have specific, compatible behavior in your application for these input types, specially around things like keyboards and mice. [02:25](https://www.youtube.com/watch?v=iztM_PgoCjw&t=145s) In short, you should build support for different input types regardless of the form factor. [02:31](https://www.youtube.com/watch?v=iztM_PgoCjw&t=151s) So, let's see how Jetpack Compose helps your apps meet these user needs. [02:37](https://www.youtube.com/watch?v=iztM_PgoCjw&t=157s) The philosophy of Compose, in general, is that these different input types should behave appropriately out of the box with standard components without you needing to do anything special. [02:46](https://www.youtube.com/watch?v=iztM_PgoCjw&t=166s) And then, you should have the tools that you need to customize and tweak the behavior as you see fit. [02:54](https://www.youtube.com/watch?v=iztM_PgoCjw&t=174s) So, beyond those initial input types, as more input types become supported by the underlying platform, we aim to build support for them into Compose components, improving behavior for your applications just by updating your version of Compose. [03:10](https://www.youtube.com/watch?v=iztM_PgoCjw&t=190s) We're going to take a look at two such examples in this talk, both recent additions to Compose. [03:14](https://www.youtube.com/watch?v=iztM_PgoCjw&t=194s) Coincidently, both are trackpad-like devices, but as you'll see, they're used in very different ways. [03:22](https://www.youtube.com/watch?v=iztM_PgoCjw&t=202s) So, let's start with some recent work on trackpads of the laptop-style variety. [03:28](https://www.youtube.com/watch?v=iztM_PgoCjw&t=208s) A trackpad is a pointer-based input device. [03:31](https://www.youtube.com/watch?v=iztM_PgoCjw&t=211s) More specifically, when you use a trackpad, you get two-dimensional coordinates. [03:36](https://www.youtube.com/watch?v=iztM_PgoCjw&t=216s) But unlike a touchscreen, those coordinates of the fingers on the trackpad aren't directly in your UI's coordinate space. [03:42](https://www.youtube.com/watch?v=iztM_PgoCjw&t=222s) Instead, for a laptop-like trackpad, there's a translation to controlling a cursor. [03:48](https://www.youtube.com/watch?v=iztM_PgoCjw&t=228s) Inside this translation, you can expect a bunch of complicated platform conversion: things like cursor acceleration and palm rejection. [03:56](https://www.youtube.com/watch?v=iztM_PgoCjw&t=236s) So, what you get then on the Compose side is a pointer corresponding to the cursor location that acts usually like a mouse input. [04:04](https://www.youtube.com/watch?v=iztM_PgoCjw&t=244s) From the Android platform, we get hover information, clicks including secondary or tertiary clicks, and all this information is wrapped up into MotionEvents. [04:14](https://www.youtube.com/watch?v=iztM_PgoCjw&t=254s) To understand how we've improved behavior in the latest versions of Compose, let's first look at a broad overview of how Compose handles these pointer events more closely. ### The six phases of pointer input handling [04:24](https://www.youtube.com/watch?v=iztM_PgoCjw&t=264s) I'm going to split this into six different phases, and we're going to go through all of these in turn. [04:29](https://www.youtube.com/watch?v=iztM_PgoCjw&t=269s) The first phase is MotionEvents moving into Compose. [04:32](https://www.youtube.com/watch?v=iztM_PgoCjw&t=272s) So, Compose is really, under the hood, just a view: a really complicated view that does a lot, but it is just a view, which means it needs to work within the overall Android system for pointer input. [04:45](https://www.youtube.com/watch?v=iztM_PgoCjw&t=285s) So, in Compose, that means Compose, as it lives inside a normal window inside a normal activity, it gets MotionEvents: the Android platform's MotionEvents. [04:53](https://www.youtube.com/watch?v=iztM_PgoCjw&t=293s) And then Compose needs to handle them and do everything that you'd want to do within Compose around pointer input. [05:00](https://www.youtube.com/watch?v=iztM_PgoCjw&t=300s) The first phase here is translating those MotionEvents into an internal, platform-agnostic representation in Compose that we call a PointerInputEvent. [05:09](https://www.youtube.com/watch?v=iztM_PgoCjw&t=309s) That's an internal type, so you're not going to see it directly. [05:11](https://www.youtube.com/watch?v=iztM_PgoCjw&t=311s) And if you want to look at all the details of what's happening here, you can go look at MotionEventAdapter, but we're going to skip over those details here. [05:18](https://www.youtube.com/watch?v=iztM_PgoCjw&t=318s) So, now we have a platform-agnostic representation of a MotionEvent in Compose. [05:24](https://www.youtube.com/watch?v=iztM_PgoCjw&t=324s) Next, it turns out that input usually happens over time. [05:28](https://www.youtube.com/watch?v=iztM_PgoCjw&t=328s) You get a bunch of input events one after another, so it's really useful to actually know about what's happening over time. [05:35](https://www.youtube.com/watch?v=iztM_PgoCjw&t=335s) Things like gestures, velocity tracking, slop detection, all of those need to have a understanding of what's happening at each point in time. [05:42](https://www.youtube.com/watch?v=iztM_PgoCjw&t=342s) So, the next stage in this processing pipeline is to add in this time information, and it's going to do some preprocessing based on successive pointer events. [05:51](https://www.youtube.com/watch?v=iztM_PgoCjw&t=351s) In code, you can look at this as taking the current PointerInputEvent, combining it with what happened with the previous one, and then we create an time-aware InternalPointerEvent. [06:00](https://www.youtube.com/watch?v=iztM_PgoCjw&t=360s) This is another internal type, as the name might suggest, but you can think of this as a map of different pointer IDs to pointer input changes, and PointerInputChanges is a public type. [06:10](https://www.youtube.com/watch?v=iztM_PgoCjw&t=370s) That's our first one. [06:11](https://www.youtube.com/watch?v=iztM_PgoCjw&t=371s) And something that you might notice with thinking about different pointer IDs is now that we're aware of the tracking of time, we also can keep track of when pointer IDs come and go. [06:22](https://www.youtube.com/watch?v=iztM_PgoCjw&t=382s) There's not just one. [06:23](https://www.youtube.com/watch?v=iztM_PgoCjw&t=383s) If you have multiple fingers on a touch screen, you might have multiple pointers at the same time coming and going, and we need to handle that. [06:30](https://www.youtube.com/watch?v=iztM_PgoCjw&t=390s) Again, if you want to dive into the details, you can go look at PointerInputChangeEventProducer, which does all this, but we're going to skip through that. [06:37](https://www.youtube.com/watch?v=iztM_PgoCjw&t=397s) We have a lot more to get through. [06:40](https://www.youtube.com/watch?v=iztM_PgoCjw&t=400s) So, we now have a Compose representation of input, and it has time information. [06:44](https://www.youtube.com/watch?v=iztM_PgoCjw&t=404s) The next step is actually figuring out what to do with that event. [06:47](https://www.youtube.com/watch?v=iztM_PgoCjw&t=407s) Where do we send it? [06:48](https://www.youtube.com/watch?v=iztM_PgoCjw&t=408s) And that leads to Hit Path testing. [06:52](https://www.youtube.com/watch?v=iztM_PgoCjw&t=412s) So, let's imagine a situation where we have a MotionEvent being sent through, and this ComposeView is filling up the entire window, the entire screen maybe. [07:00](https://www.youtube.com/watch?v=iztM_PgoCjw&t=420s) We need to figure out where to send it. [07:01](https://www.youtube.com/watch?v=iztM_PgoCjw&t=421s) And because Compose is just a view, but we have tons of things inside of Compose, a bunch of different nodes, buttons, things that you interact with, we need to do in Compose all the work of figuring out where to send an event that happens at a specific position, and figure out which node is actually interested in it. [07:19](https://www.youtube.com/watch?v=iztM_PgoCjw&t=439s) This these layout nodes have coordinates, they have bounds, and they have shapes, so we need to figure out which one, if any, is hit. [07:25](https://www.youtube.com/watch?v=iztM_PgoCjw&t=445s) But that's also not the most general view. [07:27](https://www.youtube.com/watch?v=iztM_PgoCjw&t=447s) It turns out we like to put interactable things inside of other interactable things, you know, like putting a button inside of a list. [07:33](https://www.youtube.com/watch?v=iztM_PgoCjw&t=453s) Turns out we do that a lot. [07:35](https://www.youtube.com/watch?v=iztM_PgoCjw&t=455s) So, the full general case here is that a specific input event creates a full Hit Path that goes through every interactable node that is hit by the coordinates of the input event. [07:46](https://www.youtube.com/watch?v=iztM_PgoCjw&t=466s) So, in this case, there's the three as it goes through in the nesting hierarchy. [07:52](https://www.youtube.com/watch?v=iztM_PgoCjw&t=472s) So, we have a Hit Path, but that isn't even enough. [07:55](https://www.youtube.com/watch?v=iztM_PgoCjw&t=475s) Remember how I said we have multiple pointer input events, multiple pointers that might be down? [08:00](https://www.youtube.com/watch?v=iztM_PgoCjw&t=480s) We actually need to have a full Hit Path Tree. [08:03](https://www.youtube.com/watch?v=iztM_PgoCjw&t=483s) There's not just one Hit Path when you have multiple pointers. [08:06](https://www.youtube.com/watch?v=iztM_PgoCjw&t=486s) So, that's the next phase, creating and updating this Hit Path Tree. [08:09](https://www.youtube.com/watch?v=iztM_PgoCjw&t=489s) So, this is a more detailed look at what's going on. [08:13](https://www.youtube.com/watch?v=iztM_PgoCjw&t=493s) On the left, we have nodes that are visually laid out, and then on the bottom left, you can see the hierarchical representation, which nodes are inside of other nodes, which ones are ancestors or descendants of each other. [08:25](https://www.youtube.com/watch?v=iztM_PgoCjw&t=505s) When a pointer goes down, we keep track of that Hit Path, the full path through the tree, and then we get a Hit Path for that particular pointer. [08:34](https://www.youtube.com/watch?v=iztM_PgoCjw&t=514s) This Hit Path then becomes a tree when we get the second pointer. [08:37](https://www.youtube.com/watch?v=iztM_PgoCjw&t=517s) It may intersect with some of the same nodes as the other one, but it may interact with other ones as well. [08:43](https://www.youtube.com/watch?v=iztM_PgoCjw&t=523s) So, on the right, that is the Hit Path Tree that we are now maintaining. [08:47](https://www.youtube.com/watch?v=iztM_PgoCjw&t=527s) And pointers can come and go, so we need to maintain this over time ourselves to make sure that we get the full list of nodes that are interested in every ongoing event at this moment in time. [08:59](https://www.youtube.com/watch?v=iztM_PgoCjw&t=539s) All right. [09:00](https://www.youtube.com/watch?v=iztM_PgoCjw&t=540s) So, we have now a Hit Path Tree that we're maintaining for these input events, which means we're onto the next phase. [09:06](https://www.youtube.com/watch?v=iztM_PgoCjw&t=546s) Now, we actually know where we're going to send out these pointer input events, which nodes are interested in each specific event. [09:13](https://www.youtube.com/watch?v=iztM_PgoCjw&t=553s) Now that we know that, we can do a coordinate transformation. [09:15](https://www.youtube.com/watch?v=iztM_PgoCjw&t=555s) Turns out it's really nice if your node gets input events within its own local coordinate space, so you don't have to do a whole bunch of, you know, coordinate math yourself. [09:24](https://www.youtube.com/watch?v=iztM_PgoCjw&t=564s) So, once we know that, we can take the pointer input event, combine it with the coordinates of the node that is interested, and then this is finally creating that public PointerEvent that's going to get dispatched out to all of you and the pointer input modifier. [09:39](https://www.youtube.com/watch?v=iztM_PgoCjw&t=579s) And that is the last thing in our our phase, phase 6, finally actually sending out the event. [09:44](https://www.youtube.com/watch?v=iztM_PgoCjw&t=584s) This is a pointer input event that event has happened, and sending out to node to do whatever it'd like with it. [09:50](https://www.youtube.com/watch?v=iztM_PgoCjw&t=590s) We do this in three different passes. [09:52](https://www.youtube.com/watch?v=iztM_PgoCjw&t=592s) We have an initial pass, a main pass, and a final pass. [09:56](https://www.youtube.com/watch?v=iztM_PgoCjw&t=596s) And the reason for having three different passes, it allows nodes to communicate with each other and communicate interest in specific parts of specific pointer input events, and then consuming different parts of them as part of dispatch. [10:09](https://www.youtube.com/watch?v=iztM_PgoCjw&t=609s) So, the first pass, the initial pass, goes from ancestor to descendant. [10:14](https://www.youtube.com/watch?v=iztM_PgoCjw&t=614s) An example of where this is useful is if some outer layout node might be a scrolling container, and it knows that it has started to scroll, and it wants to stop all pointer events from reaching any of its children. [10:25](https://www.youtube.com/watch?v=iztM_PgoCjw&t=625s) This, in the initial pass, allows it to consume it, and then children won't get any more events. [10:31](https://www.youtube.com/watch?v=iztM_PgoCjw&t=631s) The second pass is the main pass. [10:33](https://www.youtube.com/watch?v=iztM_PgoCjw&t=633s) And this is the only one that goes from descendant to ancestor. [10:37](https://www.youtube.com/watch?v=iztM_PgoCjw&t=637s) As the name might suggest, this is the primary pass to do things. [10:40](https://www.youtube.com/watch?v=iztM_PgoCjw&t=640s) For example, this is where a descendant will consume a down event, starting a handling a click, and then it gets a chance to do that before any of its parents if you have nested clickables. [10:51](https://www.youtube.com/watch?v=iztM_PgoCjw&t=651s) And then the last pass, final pass, looks the same as the initial pass, where it goes from ancestor to descendant. [10:58](https://www.youtube.com/watch?v=iztM_PgoCjw&t=658s) And this is where a child can tell a parent that tell that a parent consumed part of an event. [11:04](https://www.youtube.com/watch?v=iztM_PgoCjw&t=664s) So, and maybe if a child was starting to handle a click, but then a parent started to scroll, the child can see that the parent did that, and then the child can cancel the click that it had started to handle. [11:16](https://www.youtube.com/watch?v=iztM_PgoCjw&t=676s) So, those are the three passes, and those are the six phases. [11:20](https://www.youtube.com/watch?v=iztM_PgoCjw&t=680s) These This is the foundation for everything on top of the pointer input system. [11:26](https://www.youtube.com/watch?v=iztM_PgoCjw&t=686s) All your gesture handling, scrollable, draggable, clickable, and any custom pointer input are all built on top of this foundation. ### Trackpad updates and multi-finger gestures [11:34](https://www.youtube.com/watch?v=iztM_PgoCjw&t=694s) So, with all that, we can now talk a little bit about the specifics of how we improved trackpad behavior support in Compose. [11:43](https://www.youtube.com/watch?v=iztM_PgoCjw&t=703s) So, before Compose 1.12, trackpad input events would be reported out as a pointer type of touch, since it is a finger physically interacting with a trackpad to control the cursor. [11:55](https://www.youtube.com/watch?v=iztM_PgoCjw&t=715s) However, the user expectation is that this input type acts a lot more like a physical mouse. [12:01](https://www.youtube.com/watch?v=iztM_PgoCjw&t=721s) So, we changed the pointer type from being `PointerType.Touch` to `PointerType.Mouse`. [12:05](https://www.youtube.com/watch?v=iztM_PgoCjw&t=725s) With this change, users will see a much more consistently aligned mouse and trackpad experience for any component that is looking at this information. [12:11](https://www.youtube.com/watch?v=iztM_PgoCjw&t=731s) An example here is tooltip behavior: you want to have probably the same behavior for a trackpad and mouse for tooltips. [12:19](https://www.youtube.com/watch?v=iztM_PgoCjw&t=739s) The second big change that we made around trackpad relates to the multi-finger gestures that you would do on a trackpad. [12:26](https://www.youtube.com/watch?v=iztM_PgoCjw&t=746s) Previously, Compose passed through the Android platform's clever, but sort of spooky concept of fake fingers. [12:33](https://www.youtube.com/watch?v=iztM_PgoCjw&t=753s) So, let's see what that means. [12:36](https://www.youtube.com/watch?v=iztM_PgoCjw&t=756s) When you use two fingers on a trackpad to pan, Android generates a single fake touchscreen finger that goes down where the cursor is. [12:44](https://www.youtube.com/watch?v=iztM_PgoCjw&t=764s) Then, as you move your fingers on the trackpad, the fake finger on the touchscreen moves the correct amount of distance and direction to mimic the desired behavior, and then it releases. [12:54](https://www.youtube.com/watch?v=iztM_PgoCjw&t=774s) So, two fingers moving upward on the trackpad gets translated into a single fake finger on the touchscreen, also moving up, which in turn causes a scroll down. [13:05](https://www.youtube.com/watch?v=iztM_PgoCjw&t=785s) A fun thing here is if you, like me, use the reverse trackpad scrolling feature, then you want the scrolling to actually go the other way. [13:12](https://www.youtube.com/watch?v=iztM_PgoCjw&t=792s) And the way that is achieved is flipping the direction of where the fake finger goes. [13:16](https://www.youtube.com/watch?v=iztM_PgoCjw&t=796s) So now, two fingers on the trackpad that move upwards gets translated into a single fake finger on the touchscreen moving down, which in turn causes a scroll up, the opposite of what we had before. [13:30](https://www.youtube.com/watch?v=iztM_PgoCjw&t=810s) Scaling works in sort of a similar way. [13:33](https://www.youtube.com/watch?v=iztM_PgoCjw&t=813s) When you use two fingers on a trackpad to scale, Android generates two fake touchscreen fingers that go down, and then move closer together, or further apart, depending on the gesture, to mimic the desired behavior of zooming in or zooming out. [13:49](https://www.youtube.com/watch?v=iztM_PgoCjw&t=829s) So, since Compose 1.12 for panning and Compose 1.13 for scaling, we now intercept those fake fingers. [13:58](https://www.youtube.com/watch?v=iztM_PgoCjw&t=838s) We don't send them through as the platform gives it to us, and we reinterpret them into new pointer event types: PanStart, PanMove, and PanEnd for panning, and ScaleStart, ScaleChange, and ScaleEnd for scaling. [14:10](https://www.youtube.com/watch?v=iztM_PgoCjw&t=850s) And we do this in that Android-to-Compose event conversion phase that I talked about earlier. [14:15](https://www.youtube.com/watch?v=iztM_PgoCjw&t=855s) So, we convert Fake Finger events to their appropriate types, and then we send through this platform-defined gestures to the Hit Path that corresponds to the cursor. [14:23](https://www.youtube.com/watch?v=iztM_PgoCjw&t=863s) And let's see why that matters. [14:24](https://www.youtube.com/watch?v=iztM_PgoCjw&t=864s) Why do we do this at all? [14:26](https://www.youtube.com/watch?v=iztM_PgoCjw&t=866s) So, let's say this box here is a nice image that we want to be able to scale up. [14:31](https://www.youtube.com/watch?v=iztM_PgoCjw&t=871s) When you're using two fingers on a trackpad to scale, it was possible that one of the two fake fingers could miss the element. [14:38](https://www.youtube.com/watch?v=iztM_PgoCjw&t=878s) It's Hit Path wouldn't intersect with the image, and it would start outside of the bounds. [14:42](https://www.youtube.com/watch?v=iztM_PgoCjw&t=882s) So, what this would cause instead of scaling is maybe the image would scroll instead. [14:46](https://www.youtube.com/watch?v=iztM_PgoCjw&t=886s) Then maybe if there was another scrollable element outside the image, maybe that one would also start scrolling together. [14:52](https://www.youtube.com/watch?v=iztM_PgoCjw&t=892s) This isn't what the user wanted. [14:53](https://www.youtube.com/watch?v=iztM_PgoCjw&t=893s) They wanted to scale. [14:55](https://www.youtube.com/watch?v=iztM_PgoCjw&t=895s) So, what we now do is instead of sending through those fake fingers, we now send the scaling gesture just to the element underneath the cursor position, which then leads to the correct behavior. [15:05](https://www.youtube.com/watch?v=iztM_PgoCjw&t=905s) No matter where the cursor is or how close it is to the edge of the element that you want to scale, it will now correctly scale. [15:12](https://www.youtube.com/watch?v=iztM_PgoCjw&t=912s) So, the good news for all of you is that these reinterpreted input events are used by default in components like scrollable and transformable to give higher fidelity behavior for trackpad inputs if you're using those basic building blocks. [15:24](https://www.youtube.com/watch?v=iztM_PgoCjw&t=924s) And if you're using anything built on top of them, you'll just get that improved behavior by updating your version of Compose to the latest version. [15:30](https://www.youtube.com/watch?v=iztM_PgoCjw&t=930s) And if you're writing custom pointer input modifiers, I'd encourage you to take a look at these new APIs and make sure you're supporting them to give your users the best possible experience across all these different input devices. [15:41](https://www.youtube.com/watch?v=iztM_PgoCjw&t=941s) And, of course, we also added testing APIs, that you can also check that your app is handling all these different input types correctly. [15:47](https://www.youtube.com/watch?v=iztM_PgoCjw&t=947s) So, the `performTrackpadInput` looks a lot like the existing testing APIs for mouse and touch. ```kotlin onNodeWithContentDescription("Photo") .performTrackpadInput { click(center) pan(Offset(0f, 100f)) scale(0.9f) } ``` [15:52](https://www.youtube.com/watch?v=iztM_PgoCjw&t=952s) And, using all of those, you can validate that your app is working correctly no matter what input device the user is using on your device. [15:59](https://www.youtube.com/watch?v=iztM_PgoCjw&t=959s) So, hopefully this gives you a much better idea about how a pointer event goes all the way from a finger on a touchpad all the way into your app. [16:06](https://www.youtube.com/watch?v=iztM_PgoCjw&t=966s) And, I'm now going to pass it over to Jeremy to talk about a very different way that trackpads are being used. ### Introduction to indirect input [16:13](https://www.youtube.com/watch?v=iztM_PgoCjw&t=973s) Thanks. [16:16](https://www.youtube.com/watch?v=iztM_PgoCjw&t=976s) All right. [16:17](https://www.youtube.com/watch?v=iztM_PgoCjw&t=977s) Hi, I'm Jeremy Walker. [16:17](https://www.youtube.com/watch?v=iztM_PgoCjw&t=977s) I work on input a lot, and I thought it'd be interesting if I told you some of the new stuff we're doing with Compose input and how that impacts you. [16:26](https://www.youtube.com/watch?v=iztM_PgoCjw&t=986s) And specifically with this indirect input type, which came about because of a new input based on a new form factor: intelligent eyewear. [16:36](https://www.youtube.com/watch?v=iztM_PgoCjw&t=996s) So, when you're getting input from glasses, you can do it in a couple ways: you can be looking at something, you can use your voice, you can use gestures, but I'm going to specifically talk about touchpad. [16:48](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1008s) And these are a little bit different than the ones that Alex talked about, in that they're, like, on the side of your head, they're vertical, they're very wide, and they're extremely short. [16:59](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1019s) So, when we were thinking about how would this work in Compose, if you imagine you're looking in front of you at a small button, if you're moving a cursor to try to touch it, that's not going to be a great experience, right? [17:10](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1030s) So, how are we going to support it? [17:13](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1033s) Well, this is what As a reminder, this is what Glasses UI looks like. [17:17](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1037s) It's uniquely designed to lay content seamlessly over your field of view. [17:21](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1041s) But it's very simple, like, we try to keep it simple. [17:25](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1045s) And if we look at an example here, you can see this is a list. [17:29](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1049s) It's focused on a certain element in the center. [17:32](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1052s) What do we really want to do? [17:33](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1053s) Well, we want to select it, or maybe move to the previous one or the next one. [17:38](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1058s) We don't necessarily need a screen location. [17:41](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1061s) And this one's a little bit more complicated, but you can see there's basically three options: you can hang up, you can unmute, or you can turn off the video. [17:48](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1068s) But really, you want to select it, or move to another one and select that one. [17:53](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1073s) So, we are thinking we don't really need to map directly to a location on the screen, or in this case, your eyes. [18:02](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1082s) So, yeah, so this is what it actually looks like. [18:08](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1088s) So, to give you an example, you can see here, I'm just moving between elements, and this is on the Glasses UI, and this kind of fits what we want. [18:16](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1096s) And, you can tap it. [18:17](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1097s) Now, so, we looked at Android to think like, what what exists out there that we could use to support this. [18:26](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1106s) And if you guys have ever if you get Oh, sorry. [18:29](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1109s) This is skipping ahead here. [18:30](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1110s) Let me go back. [18:33](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1113s) There we go. [18:34](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1114s) All right. [18:34](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1114s) So, if you guys have ever used one of these devices, and linked it with an Android device, you know like, you can easily move from UI elements from one to the other. [18:46](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1126s) For example, with the remote, you can see you can If you imagine or you've got it linked with Google TV, and you've got a whole grid of movies, you can press the arrow buttons to go to the one you want, and you can press the button to select it, the okay button. [19:00](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1140s) If you have a game menu and you're you have this controller, you can use the D-pad, or the joystick to move to a UI element, let's say, in a menu, and you select it. [19:08](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1148s) Same with keyboard. [19:10](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1150s) So, this kind of matches up with what we want in Compose. [19:15](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1155s) And again, here's the things, so you can kind of get an idea of what it looks like. [19:20](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1160s) And if we go a little bit further back, there was actually this touchpad type that was paired with a TV, and it actually uses something called navigation touch. [19:30](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1170s) And the idea there is it's completely not the x and y coordinates are completely not touched not tied to the screen at all. [19:36](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1176s) They're just used for gesture detection. [19:39](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1179s) So, what you do there is if you swipe right, you could imagine that's like pressing like, the arrow to go over. [19:45](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1185s) If you swipe left, you go left, up, and down. [19:47](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1187s) So, this kind of gives us mostly what we want. [19:49](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1189s) You only use the x and y for like velocity tracking to figure out those drags and swipes, etc. so, that kind of gives us most of what we want to get started on Compose. [19:58](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1198s) It's not exact because it's kind of mapped to TV, so we need to make sure we don't go the TV route, we don't mess up TVs later, so we need to make sure we split those, but that's something we'll we'll get to in a little bit. [20:09](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1209s) But it gives us kind of a starting point. [20:13](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1213s) So that we can use this type of touch navigation. ### API design for indirect input and new form factors [20:18](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1218s) So, okay. [20:19](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1219s) What's the API design going to look like? [20:21](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1221s) Well, in Compose the most general one or the most common input is touch, right? [20:27](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1227s) So, we have a lot of work around that. [20:29](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1229s) So, we had to kind of figure out how it relates to that. [20:32](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1232s) So as As a reminder, this is what the PointerEvent looks like. [20:36](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1236s) You can see here you have the type which is the down kind of move in the up, and then you have this PointerInputChange which is just a list You can think of it as fingers. [20:44](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1244s) If you have three fingers down, you're going to have three things in the list. [20:48](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1248s) So we wanted it to look a lot like this. [20:50](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1250s) Now, this isn't a UI module which is a much lower module than most developers use when they're dealing with input. [20:57](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1257s) They're going to be a layer above at the foundation layer. [21:00](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1260s) And that's where all our gesture detectors are. [21:01](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1261s) Because as developers, you don't care about all these like minute input. [21:05](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1265s) You just want to know, did a drag happen, did a click happen, did a swipe happen, and then you do your action based on that. [21:11](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1271s) So, we have a bunch of code that does that for you, and it's powered by these under the hood. [21:17](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1277s) So, then we start thinking, okay, we got to make it as easy as possible for the code existing in foundation to be able to easily support these new types. [21:24](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1284s) So then, we created a new type that looks pretty much the same. [21:28](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1288s) It just is called indirect. [21:29](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1289s) And the indirect meaning it's not a direct touch on the screen. [21:32](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1292s) So, it lets you know that, hey, this is coming Don't think about this from a touch point of view. [21:39](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1299s) Think about we're just doing gesture handling here and navigating between UI elements. [21:44](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1304s) Type is the same. [21:45](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1305s) It's got a list of fingers. [21:48](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1308s) It's IndirectPointerInputChange in this case. [21:50](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1310s) But so we did those, and it worked great. [21:54](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1314s) But then there was one more item that we came across called Primary Directional Motion Axis. [21:59](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1319s) That's quite a mouthful, but it'll make sense when I walk through it, because it was kind of interesting how it came about. [22:06](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1326s) So, how did it come about? [22:07](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1327s) Well, again, you have this touchpad on the side of your head, and it's wide and it's short. [22:15](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1335s) And so, at this point, we kind of had this idea of like, okay, we have touch navigation. [22:20](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1340s) So, we can kind of swipe left and swipe right. [22:22](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1342s) And that works nice. [22:24](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1344s) But if you think about how trackpads exist now, right? [22:27](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1347s) You When you go left or right, you're going to go left or right on the trackpad in with your UI content. [22:32](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1352s) If you go up or down, you're going to scroll through the list up or down. [22:37](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1357s) So if you try to do that with glasses, like this isn't too bad, right? [22:41](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1361s) But if you're doing this, like it's not a great experience. [22:44](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1364s) So, what we actually found is the most natural movement is obviously taps, but to swipe along the side of the glasses. [22:53](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1373s) And so we did a bunch of studies with users, and they actually expect that when you swipe horizontally, you don't actually move the content horizontally, you move it vertically. [23:05](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1385s) And you can kind of think about this if you try it in your head, like when you're swiping here, if you have a list in front of you, like going forward, you can go forward through the list or you can go back through the list. [23:14](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1394s) So, we're taking like a two-dimensional input and we're kind of trying to think about it in like a one-dimensional way. [23:21](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1401s) And that's where this idea came from for the Primary Motion Axis is that you can go horizontally and it'll go vertically. [23:29](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1409s) So, you can specify on the device itself. [23:31](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1411s) You can say, hey, like this isn't a normal trackpad. [23:35](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1415s) I want to move I want the primary direction to be horizontal, and that's kind of the main, the only really, axis I want to go along. [23:45](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1425s) And so that's where that came from. [23:50](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1430s) Okay. [23:51](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1431s) So, we're feeling pretty good there. [23:52](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1432s) The Compose is handling everything. [23:55](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1435s) And you have this nice vertical implementation. [23:58](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1438s) You can think about this. [23:59](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1439s) So, how we mapped it is now you have with the horizontal definition, we just have a forward and back. [24:06](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1446s) We don't have an up, down, left, right. [24:07](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1447s) We just have forward and back. [24:08](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1448s) And then, we can map that if you're familiar with focus with the next and the previous. [24:13](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1453s) So, now we just move to the next and previous. [24:14](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1454s) And this works great for vertical. [24:17](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1457s) And it's even nicer, too. [24:18](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1458s) It works with horizontal out-of-the-box, right? [24:20](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1460s) Cuz you just have a bunch of focused items. [24:22](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1462s) You just go to the next or the previous, and it just works with both. [24:29](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1469s) So this is a case with Compose where we kind of defined and figured this all out in Compose, but it would be really nice if the device itself could say, cuz it knows if it's a weird shape, and it knows if it wants just this primary axis, but the it has no way of defining that. [24:45](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1485s) So, then now we had to take this concept back to the platform and say, okay, we need to define this in the platform so that we can standardize it and these devices themselves can specify it, so then Compose can fully support it all the way down. [24:57](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1497s) So, there's a case where we kind of worked in Compose and went went forward in Compose and worked backward, and kind of brought it along with us. [25:06](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1506s) So that's exactly what we did. [25:08](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1508s) We created this new API so that you can specify, or device can specify that. ```kotlin InputDevice.ViewBehavior .getPrimaryDirectionalMotionAxis() ``` [25:13](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1513s) And if you remember earlier, when I talked about touch input, I was, or, touch navigation, I was talking about how it was meant mainly for TVs. [25:21](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1521s) Well, we didn't want to break those implementations. [25:23](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1523s) So, we added another field to specify this new type of glass input. [25:28](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1528s) Okay. [25:30](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1530s) So, in the end, what does that mean for you? [25:32](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1532s) Well, this is a more specific version of what you get as a gesture detector. ```kotlin // Glimmer Library Box( modifier = Modifier.fillMaxSize() .onIndirectPointerGesture( enabled = true, onSwipeForward = { /* onSwipeForward */ }, onSwipeBackward = { /* onSwipeBackward */ }, onClick = { /* onClick */ }, ) .focusTarget() ) { // App() ``` [25:40](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1540s) Out of the box, if you're using Foundation, it'll just work with the gesture detectors. [25:43](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1543s) We did the work for you, so you don't have to do anything. [25:45](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1545s) But if you want to make an app for the glasses, you can use our Glimmer library, and then we have a special gesture detector, which only gives you the, you know, so you don't have to combine them, you can use just this one, get the click, the forward frame, and the back. ### Testing indirect pointer input [26:03](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1563s) Okay. [26:03](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1563s) So, we've covered on the API working itself, in the platform and in Compose. [26:09](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1569s) What about testing? [26:11](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1571s) So, this is what touch looks like when you want to do touch testing. ```kotlin @Test clickTest { rule.onNodeWithTag(defaultTag) .performTouchInput { click(center) } } ``` [26:16](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1576s) You just say `performTouchInput` and then you give it a gesture. [26:18](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1578s) There's lots of different gestures and we kind of build up all the little events to make the gesture happen. [26:23](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1583s) How does that work at a high level? [26:24](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1584s) Well, you have the Compose UI module, you have the UI Testing module and they kind of communicate, at least input-wise, using MotionEvents. [26:35](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1595s) Also, the UI Testing module also uses a queueing mechanism, which is all based on MotionEvent. [26:42](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1602s) Why am I telling you this? [26:45](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1605s) Because you cannot programmatically create a MotionEvent that is like an indirect input event, which was great for me because that meant I had to rewrite all the communication between the modules and the queueing system. [26:59](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1619s) But I did it. [27:00](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1620s) So, what does that mean for you? [27:02](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1622s) Well, if you're used to touch input testing, this is what you get for indirect. [27:08](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1628s) You just add indirect to the name. ```kotlin @Test clickTest { rule.onNodeWithTag(defaultTag) // .performTouchInput .performIndirectPointerInput ( IndirectPointerEventPrimaryDirectionalMotionAxis.X, inputDeviceSize, ) { click(center) } } ``` [27:09](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1629s) Now, I did, I am passing some extra parameters. [27:11](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1631s) You probably recognize the first one, that's where you specify the horizontal. [27:16](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1636s) And then the input size would obviously be the size of the touchpad. [27:20](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1640s) Now, this doesn't change much. [27:23](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1643s) So, if you look at the source code, you can see I just wrote an extension function and it just looks like this. ```kotlin @Test clickTest { rule.onNodeWithTag(defaultTag) // .performTouchInput .performIndirectPointerInput { click(center) } } ``` [27:31](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1651s) Okay. [27:31](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1651s) So, that gives you, that should give you an idea of all the work we did just to support trackpads on your frames, or on intelligent eyewear. [27:38](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1658s) But we're doing these, I'm doing these exercises for eye and hand tracking as well, so there's a lot more to come. [27:47](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1667s) And so, at a high view, what do I want you to remember from my part? ### Summary and key takeaways [27:51](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1671s) So, whether you're using a trackpad with a phone or glasses or any other input, we're kind of thinking about that and we're trying to do the work ahead of time. [27:59](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1679s) So, if you're using these Foundation gesture detectors, it just works out of the box. [28:04](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1684s) That's number one. [28:05](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1685s) Number two, Compose is the primary UI toolkit, and we'll continue to explore and support new input types, both in the platform and in the toolkit, which you can see is what I did here. [28:16](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1696s) And then, obviously, for testing APIs, I want to maintain a familiar surface and paradigm so it's as easy as possible for you to migrate and test these new types. [28:27](https://www.youtube.com/watch?v=iztM_PgoCjw&t=1707s) Thank you, I hope that helped.