DEV Community

Cover image for On supporting foldables
Thomas Künneth
Thomas Künneth

Posted on

On supporting foldables

Foldable devices promise exciting new ways to present the content of your apps. Due to quite exclusive price tags the few existing models have not yet gained mass adoption. This will likely change soon as a couple of affordable devices is expected to hit market later this year. But how do we utilize new features? How can we make our apps ready for devices that are not here yet? Also, we can't buy them all, can we? :-) So how do we test these capabilities? Android has, contrary to the iPhone, had the need to support different form factors (almost) from the beginning. Consequently, different display sizes, pixel densities and aspect ratios do not challenge us. Consider this (deliberately) incomplete list of platform features:

  • Fragments
  • Alternative resources
  • Vector Drawables
  • Multi Resume
  • Multi-Window
  • Display Cutouts
  • Activity Group
  • ...

Wait, what? What the heck are Activity Groups? Relax, if you have not heard of this there's no need to bother. ActivityGroup is an abandoned predecessor to the fragments concept. The class has been present since api level 1 but was deprecated in Android 3.2. The idea is to present more than one activity at a time. For example, android.app.TabActivity (also deprecated in api level 13) extends android.app.ActivityGroup. What's interesting: you can use ActivityGroup to show several activities on screen at the same time:

Two activities

The screenshot is not very exciting, but take a look at the code:

class MainActivity : ActivityGroup() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    first.addView(startActivity(R.string.first, Color.DKGRAY, Color.LTGRAY).decorView)
    second.addView(startActivity(R.string.second, Color.YELLOW, Color.BLUE).decorView)
  }

  private fun startActivity(text: Int, textColor: Int, layoutColor: Int): Window {
    val id1 = getString(text)
    val intent = Intent(this, ChildActivity::class.java)
    intent.putExtra(KEY_TEXT, id1)
    intent.putExtra(KEY_TEXT_COLOR, textColor)
    intent.putExtra(KEY_LAYOUT_COLOR, layoutColor)
    return localActivityManager.startActivity(id1, intent)
  }
}
Enter fullscreen mode Exit fullscreen mode

ChildActivity is an ordinary activity that needs to be declared in the manifest. android.app.LocalActivityManager can load them and make them available as Windows, which in turn can easily be included in view hierarchies. So why am I telling you this? If a foldable device is unfolded the user may either use the screen real estate for one app (tablet mode), or desire to have two related apps side by side. Samsung calls this App Pair, and Microsoft's upcoming Surface Duo is expected to feature something called App Groups.

Now, you may be thinking But Android offers Multi Window Mode and its implementation as split screen. True, but here the grouping process is started by the user. If an activity wants to accompany another one this needs to be declared in the manifest. As for as I know (please do use the comments to correct me) there is no general approach for launchers and other shell-like apps. Unfortunately, LocalActivityManager isn't, either. Because as Local implies only activities of the app can be launched. Trying to summon foreign activities produces security-related exceptions. But this happens deep inside Android, while resolving packages.

Jetpack Window Manager

But we don't want to write a launcher, do we? Still, we may want to know more about the capabilities of a foldable device. That is where Jetpack Window Manager comes in handy. Currently in 1.0.0-alpha01, the component aims to

help application developers support new device form factors and
provide a common API surface for different Window Manager
features on both old and new platform versions.

To use it we need to add the following line to our dependencies section in the build.gradle file of the app module:

 implementation "androidx.window:window:1.0.0-alpha01"
Enter fullscreen mode Exit fullscreen mode

The class androidx.window.WindowManager is the main interaction point with this library. Use an instance of this class to poll the current state of the device and display, or to register callbacks for changes in the corresponding states. I will illustrate this with a sample project called WindowManagerDemo. First we need to set things up:

val backend = ExtensionWindowBackend.getInstance(this)
val wm = WindowManager(this, backend)
Enter fullscreen mode Exit fullscreen mode

The second line gets us an instance of the WindowManager class initialized with and connected to the provided visual context. In my example this refers to my activity. The second parameter points to an interface called WindowBackend. That's where the actual information comes from. You can pass a custom implementation for testing, or null to use the default one (ExtensionWindowBackend). I made this explicitly to illustrate the workflow. Now let's take a look at some of the values we can query:

val posture = when (wm.deviceState.posture) {
  DeviceState.POSTURE_CLOSED -> "CLOSED"
  DeviceState.POSTURE_HALF_OPENED -> "HALF_OPENED"
  DeviceState.POSTURE_OPENED -> "OPENED"
  DeviceState.POSTURE_FLIPPED -> "FLIPPED"
  // DeviceState.POSTURE_UNKNOWN
  else -> "UNKNOWN"
}
textview.append("Posture: $posture")
Enter fullscreen mode Exit fullscreen mode

For example, we can get the posture of a foldable device with a flexible screen or multiple physical screens. Devices with a single rigid display will always report POSTURE_UNKNOWN. To determine if a device has a hinge or one flexible display, we can access a list of DisplayFeatures. A display feature is a

distinctive physical attribute located within the display
panel of the device. It can intrude into the application window
space and create a visual distortion, visual or touch
discontinuity, make some area invisible or create a logical
divider or separation in the screen space.

We get the list through windowLayoutInfo.displayFeatures. Please make sure to execute this code only after your activity has a window attached and the layout pass has happened. In my example the corresponding lines reside in doOnLayout().

Configuring a foldable AVD

If you launch my app in an ordinary emulator the list of display features is empty. You can, however, configure the emulator with foldable support, as displayed in the screenshot above. One would expect that then at east some features are reported. At the tine of writing this article, they aren't. ExtensionWindowBackend contains a private method called initExtension(). It invokes ExtensionHelper.getExtensionImpl(), which, in turn, calls
ExtensionCompat.getExtensionVersion(). This fails, the code being executed, ExtensionProvider.getApiVersion(), throws a NoClassDefFoundError. ExtensionProvider appears to not be there. What a ride... Luckily, Microsoft has recently updated their Surface Duo Emulator to support Jetpack Window Manager.

WindowManagerDemo in the Surface Duo Emulator

Summary

So far Jetpack Window Manager is quite small, yet extremely useful. It is a great relief that Microsoft seems to be utilizing this, because if there is something our ecosystem should not face is fragmentation the the foldable area. Things might have been more easy if Google had not chosen to completely abandon the tablet market. And a foldable by Googles seems not to be on the horizon either.

Top comments (0)