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.

Sunday, January 26, 2014

Introduction to Using Android's Built-In Sensors

    One of the key characteristics of mobile devices that separates them from older platforms is that they carry a number of embedded sensors, which allow them to take readings from their environment. I have written a sample program that displays available sensors on an Android device, which then shows information and output from a selected sensor. All source code can be found here.

    The initial screen is a simple ListFragment that populates an adapter with a list of sensors on the device:

SensorListFragment.java
@Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        mAdapter = new SensorListAdapter( getActivity() );
        setListAdapter(mAdapter);

        mSensorManager = (SensorManager) getActivity()
                .getSystemService( Context.SENSOR_SERVICE );

        for( Sensor sensor : mSensorManager.getSensorList( Sensor.TYPE_ALL ) )
            mAdapter.add( sensor );
    }

    This adapter displays a simple_list_item_1 list row that is populated with the name of each sensor, so that the result looks like this on a Nexus 4:



    Using the ListFragment's built-in OnListItemClicked function, we can retrieve the sensor that has been selected by the user and pass it to another fragment that replaces the one currently attached to our activity.

SensorListFragment.java
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);

        Sensor sensor = (Sensor) getListAdapter().getItem( position );
        SensorDetailFragment mFragment = SensorDetailFragment.newInstance( sensor );
        FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.container, mFragment)
                .addToBackStack(null)
                .commit();
    }

    The sensor is passed to the SensorDetailFragment and the type is stored as an argument that can be retrieved when the fragment is attached and created.

SensorDetailFragment.java
public static SensorDetailFragment newInstance( Sensor sensor ) {
        SensorDetailFragment mFragment = new SensorDetailFragment();
        Bundle args = new Bundle();
        args.putInt( EXTRA_SENSOR_TYPE , sensor.getType() );
        mFragment.setArguments(args);
        return mFragment;
    }

    When the fragment is attached and onCreate() is called, the SensorManager system service is retrieved and stored, then the sensor is retrieved from the manager by its type.

SensorDetailFragment.java
        mSensorManager = (SensorManager) getActivity().getSystemService( Context.SENSOR_SERVICE );
        mSensor = mSensorManager.getDefaultSensor( type );

    After the TextViews, which label and display information, are initialized, the static information from the sensor is displayed using the sensor's various functions found here. The dynamic information, such as the readings from the sensor, are retrieved using the SensorEventListener. This interface consists of two methods that respond to events: onAccuracyChanged( Sensor sensor, int accuracy ) and onSensorChanged( SensorEvent event ).


    onAccuracyChanged passes the sensor that triggered the event, as well as an integer that matches with one of four values representing high, medium, low and unreliable accuracy of the given sensor.

    onSensorChanged passes a SensorEvent item that consists of various attributes such as

  • accuracy
  • timestamp - useful for throwing out data that occurs more rapidly than needed by the application, but not already associated with a sampling speed
  • sensor - the sensor that triggered the event
  • values - an array of the actual readings from the sensor. This can have indices 0 - 3 populated, depending on the type of sensor giving the reading. Some, such as the gravity sensor, give readings for values[0] - values[2] for the X, Y and Z axis of the device, while others, such as the barometer, only give readings in values[0].
    As with any listener interface, it is important to remember to register and unregister your listeners in a practical place, so as to not waste the device battery while the sensors are not being checked and their data used. In this sensor program, the listener is registered in onStart

SensorDetailFragment.java
mSensorManager.registerListener( SensorDetailFragment.this, mSensor, SensorManager.SENSOR_DELAY_UI );

and unregistered when the fragment is hidden.

SensorDetailFragment.java
    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        if( hidden )
            mSensorManager.unregisterListener( this );
    }

    While there is a lot more to sensor-use in the Android platform, especially in how the data can be interpreted and used to take your apps to the next level, I hope I have given a decent introduction to retrieving data from a list of sensors, and that the source code on GitHub can help someone out there looking to implement sensors in their own projects.