Friday, January 31, 2014

Android Random Quote Daydream

    Introduced with the release of Android 4.2, Daydream is the name of the screen saver functionality that activates when a device is docked or plugged into a charger. These daydreams can be purely visible, such as the sample program that I will be discussing, or they can interact with the user through gestures and touches in order to provide more functionality.

   In order to demonstrate the Daydream service, I have built a simple application that retrieves quotes from an online feed and displays them to the user. After a set period of time has passed, the displayed quote will fade out, and another will fade in to take its place. Some key components that I am using that are not part of the standard Android SDK are:
  • GSONRequest - a Volley adapter for JSON requests that will be parsed into Java objects by GSON. This file was written by Ognyan Bankov.
  • Volley - a Google library that handle's network requests.
    All code for this blog post can be found here.



    The starting point for all Daydream apps is a Java class that extends DreamService. The code in this example is very minimal, as all of the display is handled in our custom view, but the key function in this example is the onAttachedToWindow method that is called as soon as the Daydream is started.

public class DaydreamService extends DreamService {

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();

        final QuoteView view = new QuoteView( this );
        view.setLayoutParams( new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) );

        setContentView( view );
    }
}

    QuoteView extends a simple TextView and uses a runnable with Volley and GSON to load quotes from an online API into a Quote model and animate their transitions as the quote is swapped out.

Quote model:
public class Quote {
    private String json_class;
    private String quote;
    private String link;
    private String source;

    public void setJson_class( String json_class ) {
        this.json_class = json_class;
    }

    public void setQuote( String quote ) {
        this.quote = quote;
    }

    public void setLink( String link ) {
        this.link = link;
    }

    public void setSource( String source ) {
        this.source = source;
    }

    public String getJson_class() {
        return json_class;
    }

    public String getQuote() {
        return quote;
    }

    public String getLink() {
        return link;
    }

    public String getSource() {
        return source;
    }
}

    While using the above Quote model object is not a necessity, it does keep data organized and easy to access in the application.

API Network Requests:
private void generateQuote() {
        GsonRequest<Quote> request = new GsonRequest<Quote>(
                Request.Method.GET,
                getResources().getString( R.string.random_quote_url_json ),
                Quote.class,
                onSuccessListener(),
                onErrorListener() );

        Volley.newRequestQueue( getContext() ).add( request );
    }

public Response.Listener onSuccessListener() {
        return new Response.Listener<Quote>() {
            @Override
            public void onResponse( Quote quote ) {
                if( quote == null )
                    return;

                if( mQuote == null )
                    mQuote = new Quote();

                mQuote.setJson_class( quote.getJson_class() );
                mQuote.setLink( quote.getLink() );
                mQuote.setSource( quote.getSource() );
                mQuote.setQuote( quote.getQuote() );
                startAnimation( mFadeOut );
            }
        };
    }

    protected Response.ErrorListener onErrorListener()
    {
        return new Response.ErrorListener()
        {
            @Override
            public void onErrorResponse( VolleyError volleyError )
            {
                mQuote.setQuote( getResources().getString( R.string.volley_error ) );
                startAnimation( mFadeOut );
            }
        };
    }

The above code uses a simple Volley request as an argument to GSONRequest in order to pull the quote feed into a Quote object and fade out the currently displayed text. Once the text has completely faded out, the textview is set to the new quote and fades in using another animation.

view mid-transition

The animation objects are created and set with a listener in the initAnimation method

private void initAnimation() {
        mFadeIn = new AlphaAnimation( 0.0f, 1.0f );
        mFadeIn.setDuration( mFadeInTime );
        mFadeIn.setFillAfter( true );

        mFadeOut = new AlphaAnimation( 1.0f, 0.0f );
        mFadeOut.setDuration( mFadeOutTime );
        mFadeOut.setFillAfter( true );
        mFadeOut.setAnimationListener( new Animation.AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {
                //Do nothing.
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                displayQuote();
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
                //Do nothing.
            }
        });
    }

    The final portion of this app that must be considered is the manifest. Permission to use the client's network connection must be requested, the Daydream service must be declared and an intent for android.service.dreams.DreamService must filtered to the application in order for it to run within the system.

Manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ptrprograms.daydream">

    <uses-permission android:name="android.permission.INTERNET" />

    <application>

        <service
            android:name=".services.DaydreamService"
            android:exported="true"
            android:label="@string/app_name">

            <intent-filter>

                <action android:name="android.service.dreams.DreamService" />
                <category android:name="android.intent.category.DEFAULT" />

            </intent-filter>

        </service>

    </application>

</manifest>

    As this example shows, most of the work in this Daydream application is handled through the view. Additional features, such as click listeners and gestures, can be applied as well in order to add user interaction. While this program was fairly minimalistic, it provides an introduction to Daydream that I hope others will find useful in building their own Android screen savers.