Sunday, February 16, 2014

Writing a Muzei Plugin for Online Images

    Three days ago Roman Nurik released Muzei, an app that allows users to add plugins that change device wallpaper images after a set amount of time. Now that I have built my own plugin, I want to share what I learned and show how easy it is to build a plugin to interact with the Muzei app. The goal of this app is to pull a random cat image from the CatAPI and display it for the user. All source can be found here and the released plugin can be found here.


    Plugins for Muzei work by extending either the RemoteMuzeiArtSource or MuzeiArtSource service classes and registering them in the manifest. For this tutorial I will go over using RemoteMuzeiArtSource in order to use online resources for changing the wallpaper. The key method of RemoteMuzeiArtSource that must be implemented is onTryUpdate( int reason ). 

    @Override
    protected void onTryUpdate( int reason ) throws RetryException {
        String link;
        try {
            do {
                link = getLink();
            } while( !imageHasAcceptableExtension( link ) );
        } catch( Exception e ) {
            throw new RetryException();
        }

        setMuzeiImage( link );
    }

    When a timer in Muzei goes off, this method is called. This is where an image should be pulled from an online resource and published. If an image cannot be pulled down, the RetryException exception is thrown, and Muzei handles it by using an exponential delay to attempt recalling onTryUpdate().

    In order to load the image, the image URL is needed. I do this through the getLink() method. It uses the CatAPI to get a random redirecting link, and returns the permanent link.

    public String getLink() throws IOException {

        String link = BASE_URL;
        URL url = new URL( link );
        HttpURLConnection ucon = (HttpURLConnection) url.openConnection();
        ucon.setInstanceFollowRedirects( false );
        URL secondURL = new URL( ucon.getHeaderField( "Location" ) );

        return secondURL.toString();
    }

    Once that link is verified as a valid jpg image, it is passed to the setMuzeiImage method.

private void setMuzeiImage( String link ) {
        publishArtwork(new Artwork.Builder()
                .title(getApplication().getResources().getString(R.string.title))
                .imageUri(Uri.parse(link))
                .viewIntent( new Intent(Intent.ACTION_VIEW, Uri.parse( link ) ) )
                .build() );

        scheduleUpdate(System.currentTimeMillis() + ROTATE_TIME_MILLIS);
    }

    This method uses the Muzei method publishArtwork to create a new Artwork object with a title, the image URL, and an intent to open the original image in a browser. Had another API been used with more information associated with each image, then the title can be more related to the specific image, and the intent URL could go to a web page describing the source. Another method that Artwork.Builder could use is token, which allows for tagging image that is currently being displayed. This is useful for selecting images from a stream in order to not use the same image twice in a row. The final thing handled in setMuzeiImage is scheduleUpdate, which tells Muzei when it should attempt to call onTryUpdate() in order to load in the next new image.

    Once the Muzei service is built, we must declare it in the manifest and have it accept intents for "com.google.android.apps.muzei.api.MuzeiArtSource"

    <service android:name=".MuzeiImageGenerator"
             android:label="@string/app_name"
             android:icon="@drawable/ic_launcher"
             android:description="@string/description">
        <intent-filter>
            <action android:name="com.google.android.apps.muzei.api.MuzeiArtSource" />
        </intent-filter>

        <meta-data android:name="color" android:value="#67C7E2" />
    </service>

    The icon used is displayed in Muzei when the user views their available plugins. The image should simple, be in a circle and have a transparent center image. The label and description are displayed below the icon, and the meta-data color attribute is used to change the display icon from white to a specified color. In this case I am using a cyan. The end result looks like this:


    And with that, we have a Muzei plugin! Other options include adding a settings activity so that the user choose which sources to pull from and how often the image should change, and integrating the service with existing apps to provide users wallpapers from images in the apps. I'm excited to see what comes from this, and hope others find this tutorial useful.