Muzei 3.4

Ian Lake
muzei
Published in
6 min readJul 29, 2020

Muzei 3.4 is now available on Google Play and with it comes new functionality and Muzei API 3.4, which enables your Source to take advantage of many of these features.

Commands get an upgrade

Since the first release of Muzei, sources have been able to provide custom commands. Each command would appear in the overflow menu. When the user tapped the command, it would trigger the onCommand() method of your MuzeiArtProvider.

This approach had two big issues:

  1. It was hard for users to find a command behind the overflow menu.
  2. Due to restrictions on starting activities from the background, any attempts to start an activity from onCommand() would fail on Android 10 and higher devices.

Muzei 3.4 address both of these concerns via a new API based on RemoteActionCompat which you can see as soon as you load up Muzei 3.4.

Sources can now display their commands as icons

Yep, the new getCommandActions() API lets you associate an icon with your command and respects the use of setShowAsIcon(true) to show your command as an icon right next to the Next Artwork command (there is a limit to how many icons can be displayed, so your action may still be displayed in the overflow menu if you overuse this functionality).

The new API also uses a PendingIntent to trigger your command. This means that launching an activity from a command just means you use PendingIntent.getActivity and that works exactly the same across all API levels (even on the latest versions of Android) without the need to use Intent.FLAG_NEW_TASK either. Similarly, if you have background work, you’d use PendingIntent.getBroadcast or PendingIntent.getService to do your work.

Examples of custom commands

The UnsplashExampleArtProvider has two commands — one that shows as an icon to go to the author’s Unsplash profile and a second that lets you visit Unsplash to explore more artwork.

Creating the profile command is based on the Artwork passed to getCommandActions() (remember, each Artwork can have separate commands):

private fun createViewProfileAction(
context: Context,
artwork: Artwork
): RemoteActionCompat {
val profileUri = artwork.metadata?.toUri() ?: return null
val title = context.getString(R.string.action_view_profile,
artwork.byline)
val intent = Intent(Intent.ACTION_VIEW, profileUri)
return RemoteActionCompat(
IconCompat.createWithResource(context,
R.drawable.source_ic_profile),
title,
title, // content description for the tooltip
PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT))
}

Here, the Unsplash Example source has used the metadata field to store the profile URI for the user. For the icon, RemoteActionCompat relies on IconCompat, which lets you build an icon from a bitmap, content:// URI, a byte array, or the most common case: a resource ID. Each icon should be 24x24dp. You’ll provide a title (which will appear if the action is in the overflow menu) and a content description (which will be used as the tooltip for the icon) and the PendingIntent that should be triggered when the command is tapped.

Creating the visit Unsplash action for the overflow menu is very similar with constructing the RemoteActionCompat:

private fun createVisitUnsplashAction(
context: Context
): RemoteActionCompat {
val title = context.getString(R.string.action_visit_unsplash)
val unsplashUri = context.getString(R.string.unsplash_link)
val intent = Intent(Intent.ACTION_VIEW, unsplashUri.toUri())
return RemoteActionCompat(
IconCompat.createWithResource(context,
R.drawable.muzei_launch_command),
title,
title,
PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT)).apply {
setShouldShowIcon(false)
}
}

Muzei API 3.4 ships with an R.drawable.muzei_launch_command icon which can be used as a default icon for your command. You’ll note that we need to explicitly call setShowShowIcon(false) — the default for a RemoteActionCompat is true, so you’ll need to set it to false if you always want your command to only appear in the overflow menu.

By extracting these out into their own methods, getCommandActions() can simply return a listOf() including the two RemoteActionCompat instances returned by the methods.

Integration with the File Picker

Android has provided a mechanism for integrating your own app into the default file picker and Files app since API 19 in the form of a custom DocumentsProvider. But actually implementing one is easier said than done.

To make it easy to expose artwork from your MuzeiArtProvider to the default file picker, Muzei API 3.4 now allows you to add a prebuilt MuzeiArtDocumentsProvider to your manifest:

<provider android:name="com.google.android.apps.muzei.api.provider.MuzeiArtDocumentsProvider"
android:authorities="${yourAuthority}.documents"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>

You’ll note the android:authorities — this needs to match the android:authorities on your MuzeiArtProvider entry in your manifest plus the .documents suffix. With just that, the two providers will automatically be kept in sync. There’s no extra work you need to do besides opting in to this new API.

Note: this API is most appropriate when your provider provides multiple artwork via addArtwork or setArtwork with multiple artwork. Avoid using this for artwork like the ‘Featured Art’ source that only have one artwork.

Muzei 3.4’s ‘My Photos’ source takes advantage of this out of the box, allowing you to add one of your chosen photos to an email you’re composing in Gmail or allow manually backing up your images by copying them to your local storage or Google Drive.

The Files app on an Android 10 device showing entries associated with multiple Muzei sources

Improved performance

One of the most critical user experiences is selecting your source for the first time. Each moment the user needs to wait for that first artwork to appear stretches on forever.

Just by upgrading your source to Muzei API 3.4, you’ll find vastly improved performance when you call setArtwork or addArtwork with a large number of artwork. This can result in changes that used to take 16 seconds to now take less than a second and particularly large data sets of 1000s of images (which used to take 1–2 minutes!) now also take just a second.

Better User Onboarding for Sources

When building a custom source, the trickiest part is perhaps getting users from installing your app to actually selecting your source. This has often involved giving users instructions to manually follow.

This is even more critical when running on Android 10 or higher devices, where launchers using the standard LauncherApps.getActivityList() API will show an icon for every app. While it helps raise awareness that your app is installed, the default behavior of just linking to the system Settings screen for you app (with an Uninstall button) is not particularly useful.

Muzei API 3.4 adds two new APIs which greatly improve this critical flow:

  • The isProviderSelected() API lets you check whether your source has been selected by the user in Muzei.
  • The createChooseProviderIntent() API lets you deep link directly into Muzei’s Sources screen, scrolling directly to your source so that user can immediately select it.

This lets you verify that user has selected your source and, if not, redirect them directly to where they can go to select it.

It is strongly recommended to catch any ActivityNotFoundException raised when starting the Intent returned by createChooseProviderIntent() and falling back to getLaunchIntentForPackage() with Muzei’s package name (to support older versions of Muzei that don’t support deep linking to the Sources screen) and a link to Muzei’s Play Store listing (in cases where the user has not installed Muzei at all).

For a full example, look at Muzei Ghibli’s RedirectActivity.

And so much more…

Muzei 3.4 has plenty of other improvements:

  • Muzei 3.4 is the first version translated to other languages! If you’d like to help our crowdsourcing effort in expanding Muzei to support every language, please join our Crowdin project.
  • If you’re shipping a version of your source that can be installed directly on Wear OS devices, custom commands you publish will now be displayed (using those nice icons you provide via getCommandActions()).

Muzei API 3.4 also has its own improvements:

  • The long deprecated MuzeiArtSource API has been removed entirely.
  • The Muzei API has been completely rewritten in Kotlin. You’ll notice a number of reified versions of methods (avoiding that ::class.java mess) and the fact that Artwork is now a immutable class. You can certainly still write your source in the Java programming language, but we’d certainly strongly recommend moving to Kotlin.
  • The reference docs at api.muzei.co have been fully updated for Muzei 3.4 with improved examples and cross linking between related documentation.

Muzei has been a huge passion project for me and I can’t thank Roman Nurik for giving me the opportunity to continue to contribute.

If you’re looking for examples for upgrading your source to Muzei API 3.4, take a look at pull requests I’ve filed for:

If you’d like to help sponsor future development and provide a token of gratitude (and get into Muzei’s private alpha!), you can sponsor me on Github.

--

--

Ian Lake
muzei
Editor for

Android Framework Developer at Google and Runner