Radical Art Template for Android

35
ANDROID™ 101: ART VIEWER TEMPLATE ImageSwitcher, Animations, and Git downloading. DOWNLOAD Project git download. Richard A. Perez Android 101

Transcript of Radical Art Template for Android

ANDROID™ 101:

ART VIEWER TEMPLATE ImageSwitcher, Animations, and Git downloading.

DOWNLOAD Project git

download.

Richard A.

Perez Android 101

Table of Contents

Introduction .................................................................................................................................................. 2

Android Manifest........................................................................................................................................... 3

Build.Gradle (module.app) ............................................................................................................................ 4

Animation XML files ..................................................................................................................................... 5

UI Layout ....................................................................................................................................................... 8

Menu and Action Bar Options ...................................................................................................................... 9

MainActivity.java .......................................................................................................................................... 10

Data.java ...................................................................................................................................................... 14

ArtViewer.java ............................................................................................................................................ 14

CommonVariables.java ................................................................................................................................ 19

CommonWork ........................................................................................................................................... 19

InOutAnimationSet ..................................................................................................................................... 20

MyMediaPlayer ............................................................................................................................................ 21

MySoundPool .............................................................................................................................................. 25

NewImageSwitcherImage.java ..................................................................................................................... 27

SavePhoto.java ............................................................................................................................................. 28

JUnit Testing ................................................................................................................................................ 30

Attribution .................................................................................................................................................. 34

Introduction The art viewer template is intended to be used by developers or artists who would like to

create an Android™ application to show images with music and sound effects as well as visual page turn

effects. You should be familiar with setting up a development environment on your own. Android

Studio™ is newly released (2015) and should fit your needs for development. I have designed this guide

and the project template so that non-developers can start learning how to implement an Activity’s life

cycle methods and how to handle UI events. Before using this guide you should have your Android

Studio IDE set up as well as be able to run Hello World! And other samples so you can be confident you

are ready to run the project code here. You can expect to spend a few hours on this initial work if you

are new to programming.

Essential Reading: http://developer.android.com/about/index.html

I have a Github code repository that can be used as source reference and contains the code that

is used in this tutorial. You can use the following link. It is strongly recommended to cross reference

coding here with the Github account coding since updates and improvements will occur over time.

Useful link: https://github.com/rperez22/RadicalArtTemplate

You will need to provide your own resources to complete the template. The resources to be

included are the sound files for images saved, page turn sound effect as well as a music track to play in

the background. Images to be replaced should be added into the drawable-nodpi folder as well as adding

the image filename into the Data.PICS array in the Data.java class. You can create new Android icons

from within Android Studio. There are animation files for the page turn transitions and you can edit

them in place or, use your own, or search the SDK for animation files that you think are interesting.

There are two arrow files for the page turn buttons that can be edited as well. Finally there is a gradient

background xml file that uses three colors to create a linear gradient, you should choose a gradient

color scheme that fits your collection or theme.

The user interface can be manipulated to some extent without hurting the code behind the view

objects. For instance, the button layout can be manipulated. The button click handler will always turn

the page no matter the location of its button. The ad banner can be removed if you find it to take up

too much screen space or you simply do not want to include it. It is also possible to switch from

Admob, which is what I am using, to any other ad company that works with Android. An Android

enabled phone will be needed to run or debug the application. Simply turn on developer options in your

phone’s settings. If you are forced to use the Android emulator install Intel HAXM to help performance

of the emulator if it seems to be running slow.

Also I introduce you to testing the functionality of Android features by showing a JUnit test

project and its classes. I will show how to run the tests multiple times by running a loop in the test suite

class. You should try to test your application on as many different devices as possible to look for layout

errors or any other un-expected errors.

Click into and read the Essential Reading sections… again if needed! I’ll go into each of the

source files and explain what each class is doing. I cover the view objects that make the UI in both

portrait and landscape modes. I will also cover the menu xml and creating handlers for the menu items.

The strings.xml class and the AndroidManifest.xml will be explained.

Essential Reading: https://developer.android.com/sdk/index.html

Android Manifest The Android manifest file can be thought of as a set of declarations for your application. There are some

fields or sections that simply will not change very often but there are also a few that will need to be

updated every time you have a new version of the application to upload to Google Play™.

Essential Reading: http://developer.android.com/guide/topics/manifest/manifest-intro.html

Package

package="radical.art.template"

You will need to declare a unique package name for your application

This name will need to be unique across all Google Play Store listings as well.

The package does not have to exist in folder system.

Install Location

android:installLocation="preferExternal"

I have it set to preferExternal so that it will use external memory if possible to keep

internal memory that can be limited, available.

Essential Reading: http://developer.android.com/guide/topics/data/install-location.html

Uses-Permission

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

Declarations for enabling different functionalities to the device.

This includes among others, access to the internet and access to the phone to write

images to it and index them for use in gallery.

Permissions’ can be used for JUnit testing such as SEND_SMS for testing that the sound

will quiet so a SMS notification is heard.

Essential Reading: http://developer.android.com/guide/topics/manifest/uses-permission-element.html

Application

You can set attributes such as the phone launch icon and title of application.

android:icon="@mipmap/ic_launcher" android:label="@string/app_name"

I also include meta-data tags required to use Google Play Services.

<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />

Activity

You will need to declare where the starting point for the application is by adding the

intent filter MAIN action from LAUNCHER category to an activity.

<activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>

Essential Reading: http://developer.android.com/guide/topics/manifest/application-element.html

Build.Gradle (module.app) You will need to edit the build.gradle file as you are developing your application, to set items such as

build version and current application version.

Essential Reading: http://developer.android.com/sdk/installing/studio-build.html

The changes that will need to be made here over time will include updating the compiled SDK version.

The latest version as of this writing is 22 and build tools version is “22.0.1”.

compileSdkVersion 22 buildToolsVersion "22.0.1"

Possibly more common will be the version code and name. Your .apk files that are generated and

uploaded to your Google Play Store listing will need to be in sequential order whether in Alpha, Beta or

Production version.

versionCode 1 versionName "1.0"

Dependencies will need to be updated if you are adding AdMob ads. You will need to include the

statement so that it will compile the appropriate jar files into your application and successfully build.

compile 'com.google.android.gms:play-services:7.0.0'

Essential Reading: http://developer.android.com/google/play-services/setup.html

You will need to also include the SDK version that you are going to limit phone’s to. If you create a new

project the default values should reflect what the current largest percentile of projects should use to

reach the largest number of phones.

minSdkVersion 15 targetSdkVersion 22

Essential Reading: http://developer.android.com/guide/topics/manifest/uses-sdk-element.html

Animation XML files The Animation XML files will be used by the ImageSwitcher to animate images as they are switched and

to animate the layout on resume of the application. There is an IN and an OUT animation to be set

when switching images using the ImageSwitcher. The Animation files themselves can be found in the

anim folder in the res folder.

Essential Reading: http://developer.android.com/training/animation/index.html

Essential Reading: http://developer.android.com/guide/topics/graphics/view-animation.html

There are four animation effects to manipulate.

Alpha

Transparency effects for dissolve effects or fading effects.

Translate

Moving the object from a to b in location

Rotate

Moving the objects on a pivot point and rotating clock or counter clockwise.

Scale

Resizing the view object into or out of a focus point.

These effects can be manipulated and/or chained to create more effects. I use them in simple ways to

generate an idea then it’s just a matter of chaining values and finding the sweet spot for your animation

to have the effect you are looking for.

WarpVertical.xml

The effect was found on accident and I just had to change values a bit to find out how this could be

controlled and changed. The effect is supposed to resemble a warp and dissolve but I felt it had an

element of lift to it so I called it warp vertical.

<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:fillAfter="true" > <scale android:duration="500" android:fromXScale="1" android:fromYScale="1" android:interpolator="@android:anim/linear_interpolator" android:pivotX="50%" android:pivotY="50%" android:toXScale="0" android:toYScale="1" /> <alpha android:duration="500" android:fromAlpha="1.0" android:interpolator="@android:anim/linear_interpolator" android:toAlpha="0.0" /> </set>

Tip: Examples in the SDK use percentage for pivot points relative to view size.

Scale

android:duration="500" The milliseconds the animation should take to complete.

android:fromXScale="1 android:fromYScale="1"

Since scale is the resizing it has to be from a set size to another set size over time. This is a

float number from 0 to 1. Using 1 for both values would mean it starts in full size.

android:pivotX="50%" android:pivotY="50%"

The expanding and collapsing effect can be centered on a given point, this is the pivot point.

To have the point be centered in the object I use 50% for both.

android:toXScale="0" and android:toYScale="1"

I only have the view collapse from the sides or horizontally.

So I use 0 for X scale and 1 for Y scale.

android:toXScale="0" android:toYScale="1"

The rate of change at which the animation changes can be controlled using the interpolator.

In this example I use a linear interpolator here so the animation is a steady even one.

There are others to choose from. Read the known indirect subclasses for more variations

of interpolator in the essential reading below.

Alpha

android:fromAlpha="1.0"

The values range from 0 invisible to 1 full visibility. This means this example animation starts

fully visible.

android:toAlpha="0.0"

At the end of the animation it should be completely invisible.

Essential Reading: http://developer.android.com/reference/android/view/animation/Interpolator.html

top_left_corner_rotate_in.xml

Rotate

android:fromDegrees="180"

Start the animation

wound clockwise

android:pivotX="0%" android:pivotY="0%"

Meaning the top left

corner is the pivot point

for the swing

When rotating it will

swing the view counter

clockwise into view

android:toDegrees="0"

The stop point is when it

is in normal viewing

position

top_right_corner_rotate_in.xml

Rotate

android:fromDegrees="-180" Start the animation

wound counter-

clockwise

android:pivotX="100%" android:pivotY="0%"

The right corner is the

pivot for the swing effect.

Rotating it will swing the

view clockwise into view.

android:toDegrees="0"

The stop point is when it

is in normal viewing

position

Be creative and find animations that you think are interesting or will keep your user’s using your

application and entertained. You can use other attributes such as start offset to chain together different

animations and find ways to create unique effects. Most of the better animations I’ve been able to create

have been on accident and then I take a minute to figure what is exactly creating that effect. From there

I can start to make the effect more to my liking and to my design.

UI Layout The activity_main.xml file located in the layout and layout-land folders in the res folder and contains our

layout for the UI. The user interface for this application consists of the image switcher that will show the

images and perform the animations on switch of an image. Two buttons that will trigger the images to

switch are overlaid on top of the ImageSwitcher. Finally, there is an AdView banner that is below the

ImageSwitcher object. This layout is simple but you can safely move the position of objects without

affecting the code behind. For instance the banner could be on top of the ImageSwitcher and the

buttons could be below the ImageSwitcher completely. However this would also take more screen

space from the most important part, the images themselves.

You can make objects visible or invisible at any time and I do so when the user presses the screen. I

have it hide the UI buttons and on tap again they will be visible. This allows for full screen viewing of the

images. There is a line of code that will tell the animation controller to switch animation effect as well.

This can be removed if you prefer to force the user to use the menu in order to switch animations. For

the interaction and reaction, I kept the animation switching enabled on touch of the full screen image.

There are two layout folders one for layout and one layout-land, which is for landscape orientation of

the phone. If the layout just doesn’t work for landscape as it does for portrait then you can make

changes to these separately and Android will use whichever is appropriate for the current orientation.

Essential Reading: http://developer.android.com/guide/topics/ui/declaring-layout.html

For the current layout I use a Relative Layout. This allows for one object to be placed in a specific

location and then setting another’s relative to it.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools=http://schemas.android.com/tools android:layout_width="match_parent" xmlns:ads="http://schemas.android.com/apk/res-auto" android:layout_height="match_parent" tools:context=".MainActivity">

For instance the first object you see in the xml is the AdView that is placed on the bottom of the

screen.

<com.google.android.gms.ads.AdView android:id="@+id/adView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" ads:adSize="BANNER" ads:adUnitId="@string/banner_ad_unit_id" android:background="#000000" android:visibility="visible" > </com.google.android.gms.ads.AdView>

The ImageSwitcher is next and it is placed on top of the ad banner.

<ImageSwitcher android:id="@+id/mainImageSwitcher"

android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_above="@+id/adView" android:background="@drawable/gradient_background" android:onClick="imageSwitcherClick" />

From here we can place the buttons. With no placement declarations it will be placed on the top left of

the screen. Since the top left button will be placed in the top left corner of available space I only have to

add the declaration and Android will place it as I want it.

<ImageButton android:id="@+id/leftButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/page_left_desc" android:onClick="LeftArrowClick" android:src="@drawable/left_arrow" />

However for the bottom right button I want it on top of the ad banner and in the bottom right corner

of the ImageSwitcher. For this I use the following declarations.

<ImageButton android:id="@+id/rightButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/adView" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:contentDescription="@string/page_right_desc" android:onClick="RightArrowClick" android:src="@drawable/right_arrow" />

This will allow the button to be placed as I need it. In order to declare a view object to be placed

relatively to another you will need to declare the object it will be placed relative to first for reference to

later. It is why I declare the adView first and then reference the other views afterwards.

Menu and Action Bar Options I have the minimum sdk version set for 11 this means that the Action Bar will be supported without the

use of the support library. You can target lower than 11 and import the support library to use the

Action Bar. This will allow more devices on Android to download this application. You can use this

official site to determine what versions are most used and if you have to make changes in your SDK

version how many people it might affect.

Essential Reading: https://developer.android.com/about/dashboards/index.html

For this application I create a menu to handle most of the settings and redirect links. However since it is

available I decided to change one line in the main.xml in the menu folder. This is for the first item with id

menu_save_image. I added the attribute android:showAsAction="ifRoom". Setting this allows for a

menu option to appear in the Action Bar if there is room for it. This will allow users to see the option

while paging through images and not have to use the menu to find that they can do this.

Essential Reading: http://developer.android.com/guide/topics/ui/menus.html

After reading this reference you should have a better understanding of the code I am sharing and you

should then be able to make customizations to the application such making the blog link an Action Bar

item instead. You can add multiple items to the Action Bar but it will end up showing only what it has

room for so test the UI on different devices to make sure the layout you are hoping to create is in fact

what the user’s will see.

To create a default order edit the attribute android:orderInCategory="100". To never show the

menu item in the Action Bar use the attribute declaration app:showAsAction="never". In this sample I

only wanted the Save Image option to appear in the Action Bar next to the title

app:showAsAction="ifRoom". To set the text the user will see for each item use

android:title="@string/menu_save_image". If an icon is declared as an attribute of the item,

android:icon="@mipmap/ic_launcher", it will not show the title string it will show the icon.

MainActivity.java The MainActivity.java class is defined in the AndroidManifest.xml as the starting point to the application.

In here we see that we declare this class to extend Activity. This means there is a specific set of actions

to take at particular times that make up a layer of the presentation to the user.

Essential Reading: http://developer.android.com/guide/components/activities.html

After reading the opening comments it should be clear that the Activity class provides a way for you to

perform actions that are essential to your application. These are the Android lifecycle methods that you

will need to add code to for your application to run correctly.

protected void onCreate(Bundle savedInstanceState) protected void onResume() protected void onPause() protected void onStop() protected void onDestroy()

When a class extends another class as the MainActivity class extends AppCompatActivity class it allows

for the inclusion of methods that you will be able to use or add code to. Extending Activity provides for

the MainActivity class a set of lifecycle calls that you will implement.

public class MainActivity extends AppCompatActivity

The MainActivity is the starting point and will perform most of the actions needed but when doing the

work, the work to be performed will be often be done in other classes. This separation will allow you

refactor the Activity into Fragments if you choose to, more easily.

@Override protected void onStop() { super.onStop(); artViewer.stop(); }

The NoisyAudioStreamReciever class will be used to quiet the sound of the application due to unusual

audio noise. Most likely this will be due to the user unplugging their headphones. For the receiver to

work a BroadcastReceiver will need to be implemented and we can tell it to listen for an Intent that will

specifically have the action of ACTION_AUDIO_BECOMING_NOISY. The receiver itself uses the onReceive

method to handle the sounds and you will have to check intent’s action is

ACTION_AUDIO_BECOMING_NOISY before you quiet the sound.

private class NoisyAudioStreamReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent .getAction())) { artViewer.quietSound(); } } }

The receiver should be registered and unregistered in the pause and resume methods of the

MainActivity. Here we create a new receiver and start listening in OnResume and don’t stop until the

application calls the onPause method. This is all that is needed for the receiver to work, it is running

silently in the background simply waiting for the audio disruptions to be caught. When you plug in the

headphones it will signal AUDIOFOCUS_GAIN in the MediaPlayer and this will return the volume to the

previously set volume. When you add the declaration implements OnAudioFocusChangeListener in the

MediaPlayer class it will allow you to handle the audio focus changes and change the volume as needed.

startPlayback(); private void startPlayback() { registerReceiver(myNoisyAudioStreamReceiver, intentFilter); }

The work to quiet the sound will be performed in MyMediaPlayer class. Once in the quietSound method

of the ArtViewer.java class you can see that the volume will be changed if the MediaPlayer is not null.

artViewer.quietSound(); public void quietSound() { // set volume to low if (myMediaPlayer != null) { myMediaPlayer.setNewVolume(0.1f); } }

I include an instance of the CommonVariables class which is a class to hold variables that will be used

across the different classes. These are meant to be shared or have a value that is meant to be saved for

some amount of time and retrieved at a later time. For example the current sound position is stored

when the MediaPlayer is paused. Since the program logic will move from class to class, the sound

position will need to be retrieved at any time with access to the correct current value being essential.

CommonVariables cv = CommonVariables.getInstance();

I also include the ArtViewer class into the activity and you’ll find that most of the time the work to be

done is handed off to this class. If you decide to use a Fragment in your development you will find that

this class can be easily implemented into the Fragment and while you may have to refactor to some

degree you will not have to move all components simply the ArtViewer class itself.

ArtViewer artViewer = new ArtViewer();

onCreate

Starting the Android lifecycle calls with onCreate we first set the Content View this will not only tell

Android what layout file to use but it will call the constructors for the UI objects as well. If you open the

layout folder and look at the activity_main.xml class you’ll see it has four UI objects and after setting the

content view you can store a reference to them and be able to manipulate them from this point on.

setContentView(R.layout.activity_main);

Once the application is updated a call to initialize the ArtViewer is made and we send an instance of the

current class.

artViewer.init(this);

onResume

From here we create a new NoisyAudioStreamReceiver and call the StartPlayback method to begin

listening for unusual noise.

myNoisyAudioStreamReceiver = new NoisyAudioStreamReceiver(); startPlayback();

From here we can start the music if the application should be playing music. At this point the

SharedPreferences from the last state of the application have been loaded. If the application should not

play music what will happen then is that we call the method abandonFocus(). This tells the MediaPlayer

to release the audio to the device itself to use. If you started the application and you were playing music

on your phone then it will return audio to it while using the application. For the design of the application

the music player and sound player are separate classes so the sound effects will continue to play even if

the music is turned off allowing for separation of the different type of sound settings.

if (!cv.playMusic) { artViewer.myMediaPlayer.abandonFocus(); }

Next we can animate the view for the user so they can have some sort of visual cue that the application

is ready to be interacted with. First we create an animation by using AnimationUtils to load the

Animation into an Animation variable and resetting it. To start the animation first get a reference to the

entire layout that makes up the user’s view, this is defined in the MainActivity.xml in the layout with the

id relativeLayout. You can get a reference to this as a view object by finding the layout by id and then

clearing the animation and starting the animation.

Animation anim = AnimationUtils .loadAnimation(commonVariables.context, R.anim.expand); anim.reset(); View relativeLayout = findViewById(R.id.relativeLayout); relativeLayout.clearAnimation(); relativeLayout.startAnimation(anim);

onPause

Stop the playback of our receiver listener by calling stopPlayback in the onPause method. After we have

stopped the receiver from doing more work we can call the pause method of the ArtViewer class.

@Override protected void onPause() { super.onPause(); // unregister headphone listener try { stopPlayback(); } catch (Exception ignored) { } artViewer.pause(); }

onStop

This method being called means the state of the application needs to be saved, the work to do this will

be passed on to the ArtViewer class.

@Override protected void onStop() { super.onStop(); artViewer.stop(); }

onDestroy

The last lifecycle call to be made will be to onDestroy(). The artViewer.destroy() method will

contain work to do the final releasing of components AdView, MediaPlayer and finally the SoundPool.

Without releasing these components you will continue to use memory needlessly this will affect

performance of your device if not handled correctly. You have the official developer pages where you

can read the documentation for the MediaPlayer read the source and comments for further information.

Essential Reading: http://developer.android.com/guide/topics/media/mediaplayer.html

@Override protected void onDestroy() { super.onDestroy(); artViewer.destroy(); }

User Defined Methods

The MainActivity class can handle button clicks from the UI without creating a listener. This means that

in the layout file when creating the button we can declare the onClick attribute. In the Activity we can

include this method and see that we are passed a View object named view. Getting the view as a

parameter will allow us to have a reference of the view object clicked.

android:onClick="imageSwitcherClick" public void imageSwitcherClick(View view){ artViewer.imageSwitcher(view); }

Data.java The Data class I have created is for variables that might change from time to time should be expanded

on as you add more images or change the background music track. The array of integers PICS holds a

reference to every image you want the application to use. When you add images to your drawable-

nodpi folder you will have to add the title of the image to the array here in order for the code to be

able to select it when choosing the next image to show to the user.

public final static int[] PICS = { R.drawable.image0, R.drawable.image1, R.drawable.image2, R.drawable.image3… };

The path is a reference to the package of the project and will be used when referencing the music track.

You will have to update this when preparing to host your application online. The address here uses the

package declaration from the AndroidManifest.xml file. So when you change your package name, and you

will have to do this if you are planning on hosting your application online, you will need to update this

line of code for the system to be able to use the track.

public static String PATH = "android.resource://radical.art.viewer/";

For the background music track I add a variable here as a reference to the track. This combined with

the path will be used in order to tell the MediaPlayer what track should be used and where to find it. If

you add a music track that has a different name than track_01.mp3 then you will need to update the

reference here in the coding.

public static int TRACK_01 = R.raw.track01;

ArtViewer.java The ArtViewer class is one of the more important classes from the standpoint that the application

redirects to here from the MainActivity class in most lifecycle methods. Most of the work to be done is

directed to here first and either the work is done here or passed to another class. An application when

created will have a starting activity but I encapsulate the work in this way and its helped make using

Fragments easier and should be helpful to you when you decide to expand on this project.

The next class InOutAnimationSet is used to get Animations for our ImageSwitcher. We will have an

image shown and on change of an image it will get an animations for the current image as it moves out

of the user’s view and we will get an Animation for the image moving into view. There will be more

explanation on this class’s usage in its own class heading.

InOutAnimationSet inOutSet = new InOutAnimationSet();

Next we have the ImageSwitcherTask class that is defined as an AsyncTask meaning it will be able to be

use the methods of the AsyncTask class such as the doInBackground method and onPostExecute. This

will allow us to perform work off of the main Android thread. Too much work being done

synchronously will cause your application to slow down. Implementing the AsyncTask class will improve

your applications performance.

AsyncTask<Object, Object, BitmapDrawable> imageSwitcherTask;

init(Context context)

We use SharedPreferences to save variables that will be needed upon returning. Since we are handling

onStop logic in this class we can use an instance of SharedPreferences here for retrieval of the saved

values. First we get the saved variables, if possible, and then start recreating the state of the application

such as loading the image and playing the background track.

Next we continue to initialize the ArtViewer by getting the saved setting of the previous usage of the

application by getting the SharedPreferences we last saved if any. For this application most of the

SharedPreferences are saved independently of each other, meaning that resuming the MediaPlayer from

the last position will not affect the last viewed image and vice versa so if either should fail it won’t cause

problems for the other. If we set default values for the variables we can attempt to retrieve them and

know they will be valid in case the retrieval fails.

if (sharedpreferences.contains(vars.res .getString(R.string.current_image))) { vars.currentImagePosition = sharedpreferences.getInt( vars.res.getString(R.string.current_image), 0);

}

Next we have the MediaPlayer and SoundPool classes that I have separate classes created for. The main

difference is that the MediaPlayer is intended for playing music that will not be loaded and played as

frequently as a game’s sound effects that can be short and repetitious. These will be using SoundPool.

This separation will allow you to actually turn off the music and play your own while keeping the sound

effects playing if you desire. Both classes will have more details explained in their own sections.

public MyMediaPlayer myMediaPlayer = new MyMediaPlayer(); MySoundPool mySoundPool;

Next we can store a reference to our ImageSwitcher to handle showing and switching images. The

reference isn’t all to be done, the source documentation says we need to make a call to setFactory to

set the factory used to create the two views between which the ViewSwticher will flip between. The

views will fit in the center of the screen, using match parent will take up as much screen as possible.

imageSwitcher = (ImageSwitcher) act.findViewById(R.id.mainImageSwitcher); imageSwitcher.setFactory(new ViewFactory() { @Override public View makeView() { ImageView myView = new ImageView(act.getApplicationContext());

myView.setScaleType(ImageView.ScaleType.FIT_CENTER); myView.setLayoutParams(new ImageSwitcher.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); return myView; } }); imageSwitcher.setBackgroundResource(R.drawable.gradient_background); imageSwitcherTask = new NewImageSwitcherImage(imageSwitcher) .execute(new Object());

Finally, there is an AdView object that we will create a reference to and start to load the ad using the

adRequestBuilder.build method.

adView = (AdView) ((Activity) context).findViewById(R.id.adView); adView.setBackgroundColor(Color.BLACK); final AdRequest.Builder adRequestBuilder = new AdRequest.Builder(); adView.loadAd(adRequestBuilder.build());

Left(View view) and Right(View view)

You can see we use the simply named method left and right to perform switching of the images in

response to button clicks. Here we advance the current image position and reset it to the end or

beginning if needed to create a loop through all images effect.

vars.currentImagePosition--; if (vars.currentImagePosition < 0) vars.currentImagePosition = Data.PICS.length - 1; vars.currentImagePosition++; if (vars.currentImagePosition > Data.PICS.length - 1) vars.currentImagePosition = 0;

Next we set the animation for the views to perform. And then if there is a current image loading we can

cancel that task and start a new task with the most current image number. Finally we can play the page

turn sound to for additional feedback to the user and to make your application livelier

Animation in = inOutSet.getInAnimationRight(vars.pageTurnMode); Animation out = inOutSet.getOutAnimationRight(vars.pageTurnMode); imageSwitcher.setInAnimation(in); imageSwitcher.setOutAnimation(out); if (imageSwitcherTask != null) imageSwitcherTask.cancel(true); imageSwitcherTask = new NewImageSwitcherImage(imageSwitcher) .execute(new Object()); mySoundPool.playPageTurnSound();

onOptionsItemSelected(MenuItem item)

The options selected from the menu will be directed to here and we will be able to handle them

individually. Toggle music and sounds will be handled in the MyMediaPlayer and MySoundPool classes.

The URL link options will redirect to websites using an Intent to start a new Activity then open the

browser and traverse to the links provided in the strings.xml class. The save the current image option

will create a new SavePhoto class instance to save the current image to your phone. Finally you can

change the page turn animations here which will cycle through the three default page turn animations I

have created. Afterwards a toast message will show which current animation is being used.

public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_blog_devart: Intent i1 = new Intent(Intent.ACTION_VIEW, Uri.parse(vars.res .getString(R.string.deviant_link))); vars.context.startActivity(i1); return true;

We set an onClick handler for our imageSwitcher in full screen view of the image currently being

viewed. First it will get a reference to each button and then either hide or show the buttons. I included a

call to toggle the page turns as well from here. Commenting out the call to toggle page turns will allow

you to change this setting and the user will only be able to change animations using the menu option.

public void imageSwitcher(View view) { // hide or show full image if clicking on view and not a page turn Activity act = (Activity) vars.context; ImageButton left = (ImageButton) act.findViewById(R.id.leftButton); ImageButton right = (ImageButton) act.findViewById(R.id.rightButton); if (right.getVisibility() == View.VISIBLE && left.getVisibility() == View.VISIBLE) { right.setVisibility(View.INVISIBLE); left.setVisibility(View.INVISIBLE); } else { right.setVisibility(View.VISIBLE); left.setVisibility(View.VISIBLE); // as a bonus change the mode for the animations togglePageTurns(); } }

The next call will be to quiet the sound of the application, this will redirect to the MyMediaPlayer

instance, if not null, and send to it a value of 0.1f which will be used to keep the volume at a very low

setting until the audio focus receives gain.

public void quietSound() { // set volume to low if (myMediaPlayer != null) { myMediaPlayer.setNewVolume(0.1f); } }

The next methods will be to handle the lifecycle calls such as resume, which will resume the adView

banner to the user’s view and will resume the MediaPlayer and SoundPool to be ready for use.

public void resume() { adView.resume(); if (vars.playMusic) myMediaPlayer.resume(); }

The pause method will pause the AdView object and pause the MediaPlayer. Being paused removes

audio focus from the application and allows external applications music to be played.

public void pause() { adView.pause(); myMediaPlayer.pause(); }

Stop will stop the MediaPlayer if playing and save position and then save all the application settings using

SharedPreferences.

public void stop() { if (myMediaPlayer != null) myMediaPlayer.onStop(); Editor editor = sharedpreferences.edit(); editor.putInt(vars.res.getString(R.string.current_image),

vars.currentImagePosition); editor.putInt(vars.res.getString(R.string.turn_mode), vars.pageTurnMode); editor.putBoolean(vars.res.getString(R.string.play_music), vars.playMusic); editor.putBoolean(vars.res.getString(R.string.play_save), vars.playSaveSound); editor.putBoolean(vars.res.getString(R.string.play_page_turn), vars.playPageTurnSound); editor.putInt(vars.res.getString(R.string.current_position), vars.currentSoundPosition); editor.apply(); }

Finally in the destroy method we can release resources associated with MyMediaPlayer and

MySoundPool as well as the AdView object itself.

public void destroy() { if (mySoundPool != null) { mySoundPool.release(); mySoundPool = null; } if (myMediaPlayer != null) { myMediaPlayer.cleanUp(); myMediaPlayer = null;

} adView.destroy(); }

CommonVariables.java The CommonVariables class contains the common variables that will be used across classes and will be

updated in one class and yet expected to retain the same value if the value is retrieved in another class.

If the concept of a Singleton class is new to you then you should review design patterns wiki below to

become more familiar.

Essential Reading: http://en.wikipedia.org/wiki/Singleton_pattern

I will summarize it for you as a class that you will need many times but you only create once. To help

guarantee that you create it only once you will need to declare it as volatile, meaning that it will go

directly to the memory with no caching to create or retrieve it. You will have to synchronize access to

it to avoid concurrency problems associated with multithreading or multiple points of possible access to

getting the instance of the singleton.

If you are familiar with creating new classes in java by calling its constructor you will find the Singleton

pattern to be unique in that you actually get the class by calling its .getInstance() method this will either

create the singleton for you behind the scenes or will return the one created. This way no matter who

calls for it, there is only ever one created.

public static CommonVariables getInstance() { if (instance == null) synchronized (CommonVariables.class) { if (instance == null) instance = new CommonVariables(); } return instance; }

The constructor is then private to make sure that no other class can create itself other than the

singleton itself through the static method getInstance()

private CommonVariables(){}

All of the variables used in the application that should be shared across classes and retain their values

are stored in this class. Variables for settings such as sound on and off are used across different classes

such as the ArtViewer and the MyMediaPlayer classes, the value should be consistent but the access to

them should be simple. Using a singleton class will limit one instance of all the variables declared in it.

CommonWork The common work to be done in the CommonWork class is to create Toast messages for the user.

This is a simple popup message that can be overwritten if consecutive Toast messages are created for

the user.

public void showToast(Context cont, String message) {

// create if not, or set text to it if (toast == null) { toast = Toast.makeText(cont, message, Toast.LENGTH_SHORT); toast.setGravity(Gravity.BOTTOM | Gravity.CENTER, 0, 0); } if (!toast.getView().isShown()) { toast.setText(message); toast.show(); } else { toast.cancel(); toast.setText(message); toast.show(); } }

The showToast method takes the application context as a parameter and the string message you would

like to show to the user. If the Toast is null it is likely the first time the method is being called. If this

Toast is not currently being shown we can set the message to the current Toast and show it. Otherwise

we can cancel out an existing message and overwrite it with the new message. This will prevent multiple

Toast messages from being created and forming a queue for the user that might end up stacking and

showing even after the application has been closed.

InOutAnimationSet The animations used to set the new view for the user and move the current image out of the user’s

view are stored and cycled through in this class. There are two Animations outAnimation and

inAnimation that will be set to the ImageSwitcher views. I include the CommonVariables class for access

to the context. There is a final int for the total animation count that can be set. In this project there are

three different animations that I have created. Each Animation retrieval method switches between these

three repeatedly when the user selects the menu option or taps on the image two times.

public Animation getInAnimationLeft(int anim) { switch (anim) { case WARP_VERTICAL: inAnimation = AnimationUtils.loadAnimation(vars.context, R.anim.fade_in); break; case COLLAPSE_ROTATE_CENTER: inAnimation = AnimationUtils.loadAnimation(vars.context, R.anim.top_left_corner_rotate_in); break; case EXPAND_COLLAPSE: inAnimation = AnimationUtils.loadAnimation(vars.context, R.anim.expand); break; } return inAnimation; } }

MyMediaPlayer The MediaPlayer class is Android’s music player class, it should be used to play music tracks and if not in

use should be released to free up resources that save your phone’s resources and battery power. Read

both the Android source documentation and the developer pages for media playback for a full

understanding as well as read the supported media formats section for further information on the topic.

Essential Reading: http://developer.android.com/reference/android/media/MediaPlayer.html

Essential Reading: http://developer.android.com/guide/topics/media/mediaplayer.html

Essential Reading: http://developer.android.com/guide/appendix/media-formats.html

CommonVariables and CommonWork classes are used for access to variables and toast messaging.

CommonVariables cv = CommonVariables.getInstance(); CommonWork cw = CommonWork.getInstance();

We also create a path for the MediaPlayer to find the music track that you are using. The path will be a

combination of the Path that is declared in the Data.java class and the track that is declared in the

Data.java class.

Uri path = Uri.parse(Data.PATH + Data.TRACK_01);

We can track the MediaPlayer’s state by getting and setting the currentState variable of the

MyMediaPlayer class. The MediaPlayer is state based and in creating your own set of States and methods

you should follow the documentation and diagrams supplied in the above readings.

enum States { Idle, Initialized, Prepared, Started, Preparing, Stopped, Paused, End, Error, PlaybackCompleted

} public States currentState;

This may be one of the better learning experiences you can have with Android, which is learning how to

code this from the state diagram and filling in logic from the source documentation as recommended. So

I will go over the MediaPlayer as best I can to give you an idea of how I implement it. You will need to

be sure to test your media player for playback and resuming as it should work.

The init method begins setup by creating a result from the request for audio focus by the application.

public void init() { am = (AudioManager) cv.context.getSystemService(Context.AUDIO_SERVICE); result = am.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);

if (mediaPlayer == null || currentState == States.Error || currentState == States.PlaybackCompleted) {

mediaPlayer = new MediaPlayer(); mediaPlayer.setOnPreparedListener(this); mediaPlayer.setOnErrorListener(this);

mediaPlayer.setOnCompletionListener(this); mediaPlayer.reset(); currentState = States.Idle; } }

From here we can create a new MediaPlayer and reset it. After this we add the listeners that are needed

to perform standard MediaPlayer actions. You can tell these three have this as a parameter after each

and we can do this in the coding because in the class declaration for MyMediaPlayer we are

implementing three interfaces that work directly with the MediaPlayer class itself, the

OnPreparedListener, the OnErrorListener, and the OnCompletionListener.

public class MyMediaPlayer implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, AudioManager.OnAudioFocusChangeListener, MediaPlayer.OnCompletionListener {

We implement the OnAudioFocusChangeListener but do not attach a listener to the MediaPlayer. The

application will call public void onAudioFocusChange(int focusChange) once we declare the

interface. After calling reset we put the MediaPlayer into the Idle state.

If we have focus we can check that we called the start method and that the MediaPlayer state is in the

Idle state. If not we can call init to do this. After which we can start to prepare the MediaPlayer if it is

not already Preparing. Doing this successfully we can continue to set the data source which will use

the context of the application and the path as defined earlier. If you changed the package declaration as

you should have, you will need to make sure the PATH variable in the Data.java class is accurate and

uses the same package as is in your AndroidManifest.xml file.

public void start() { if (cv.playMusic) { if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { if (currentState != States.Idle) init(); if (currentState != States.Preparing) { try { mediaPlayer.setDataSource(cv.context, path); currentState = States.Initialized;

mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setVolume(cv.volume, cv.volume); mediaPlayer.prepareAsync(); currentState = States.Preparing; } catch (IllegalArgumentException e) {} catch

(SecurityException e) {} catch (IllegalStateException e) {} catch (IOException e) {} } } } }

We set the MediaPlayer to play using the music stream and set the volume to the stored volume setting

from when we first created the application. We can then make a call to prepareAsync(). While this is

happening there is a chance the coding logic could make its way here again to the start method and we

will not want to prepare it again if the first preparing has not failed or completed. To prevent this we

put the current state into the Preparing state. We will also need to catch any exceptions that might

come up from this method so the application can recover from error conditions.

After the work of the asynchronous call prepareAsync() is finished the method onPrepared is called.

@Override public void onPrepared(MediaPlayer player) { // check for option to play music and resume last position if (currentState == States.Preparing) { currentState = States.Prepared; if (cv.playMusic) { if (cv.currentSoundPosition >= 0) { mediaPlayer.seekTo(cv.currentSoundPosition); } if (currentState != States.End && !player.isPlaying()) { player.start(); currentState = States.Started; } } } }

The current state should be Preparing. If ready we can put the put the media player in the Prepared

state and if the stored variable for play music is set to true we are ready to move the media player into

the position we have last saved. Finally check the MediaPlayer isn’t released and make sure it isn’t

already playing. After which we can start the player and set the state as Started. If the MediaPlayer

does reach the OnError listener we will have to put the MediaPlayer in the Error state and re-start it.

We can do this by calling the start method, if the MediaPlayer is not in the Idle state in the start

method it will reset the MediaPlayer before attempting to resume playback.

@Override public boolean onError(MediaPlayer mediaPlayer, int i, int i2) { currentState = States.Error; init(); return true; }

Essential Reading:

http://developer.android.com/reference/android/media/MediaPlayer.OnErrorListener.html

We use the OnAudioFocusChange method to handle the cases where the audio focus has changed this

included the AudioFocus cases.

@Override public void onAudioFocusChange(int focusChange) {

AUDIOFOCUS_GAIN

The MediaPlayer should resume playing at normal volume.

AUDIOFOCUS_LOSS

The focus is lost for what may be an extended duration.

We will have to release focus for the device and pause if playing, stop and then release

the MediaPlayer and put the current state into the End state.

Setting the media player as null allows the resources associated with it to be freed.

AUDIOFOCUS_LOSS_TRANSIENT

Android may see it as a temporary loss of focus but we still need to pause the

MediaPlayer and record the position it stopped at.

AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK

In this case we simply reduce the volume for notifications and it is expected that the

sound will return shortly.

This will be returned to the previous volume in the AUDIOFOCUS_GAIN case.

When we resume the playing of music after returning to the application or from the user pressing the

menu option for toggling the music we can restart the media player by calling init and start methods in

succession. This will attempt to re start the media player.

public void resume() { // media player should have been destroyed in last pause init(); start(); }

Whenever we set a new volume I track this new volume. We don’t want to set a new volume if the

current state is End, likely from shutting down. If the mediaplayer is not playing we don’t want to set a

new volume to it. This is a call from coding logic in the application and not a method that is called

because the user is changing volume manually.

public void setNewVolume(Float setVolume) { if (currentState != States.End && mediaPlayer.isPlaying()) { mediaPlayer.setVolume(setVolume, setVolume); } } The menu option for disabling and enabling the music is performed in the toggleMusic() method.

After which we simply set a flag to know what to do at that time and inform the user via our common

work class that the music has been resumed or shut off.

public void toggleMusic() { if (cv.playMusic) { cv.playMusic = false; cw.showToast(cv.context, "Music off"); pause(); } else { cv.playMusic = true; cw.showToast(cv.context, "Music on/restarted"); resume(); } }

The onCompletion listener is used to set the state of the MediaPlayer to PlaybackCompleted and we

will need to reset our sound position place to zero so it will play from the beginning on start. If you do

not want the music to loop you can comment the start call out if you like.

public void onCompletion(MediaPlayer mp) { currentState = States.PlaybackCompleted; cv.currentSoundPosition = 0; if (mp != null) { mp.release(); mp = null; } init(); start(); }

When stopping the application we want to make a call to onStop on our MediaPlayer that way we can

correctly pause and set the state to Paused. At this point we can get the current position for resuming.

public void onStop() { if (mediaPlayer != null) { if (mediaPlayer.isPlaying()) { mediaPlayer.pause(); currentState = States.Paused; cv.currentSoundPosition = mediaPlayer.getCurrentPosition(); } } cleanUp(); }

When leaving the application we will want to release the media player if it is not null and set the current

state as the End state. Finally we set the MediaPlayer to null to let Android free the resources associated

with the MediaPlayer.

public void cleanUp() { // a final check when the app closes down for good if (mediaPlayer != null) { mediaPlayer.release(); currentState = States.End; mediaPlayer = null; } }

MySoundPool The SoundPool class should be used to play short sound snippets, in rapid succession if needed. I use a

class that extends SoundPool so it does everything a SoundPool does but I can add in extra functionality

that is used in the application. The only variables included here are the CommonWork and

CommonVariables class you should be familiar with by now. You can read the documentation provided

by Android below for further information on the Android SoundPool.

Essential Reading: http://developer.android.com/reference/android/media/SoundPool.html

The default constructor is used in this version although as of Lollipop you can use a SoundPool builder

to create the SoundPool object. For the constructor I wanted to load two separate sounds and create a

flag for each so that when it comes time to play the sound the sound pool will know whether the sound

was loaded correctly. You can see there are two load calls and onLoadComplete each save variable has

a Boolean loaded variable associated with it. This will be the flag at run time as to whether or not the

device will play these sounds.

@SuppressWarnings("deprecation") public MySoundPool(int maxStreams, int streamType, int srcQuality) { super(maxStreams, streamType, srcQuality); // create a new sound pool and set up sounds this.setOnLoadCompleteListener(new OnLoadCompleteListener() { public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { if (sampleId == vars.saveSound) vars.saveSoundLoaded = true; else if (sampleId == vars.pageTurnSound) vars.pageTurnLoaded = true; } }); vars.saveSound = load(vars.context, R.raw.imagesaved, 1); vars.pageTurnSound = load(vars.context, R.raw.pageturn, 1); } On play of either sound we will need to perform two checks before attempting to play the sound. Frist

we will make sure the sound was loaded on the start of the application and then we will need to check

that the user has not toggled the sounds off via the menu button.

public void playSaveSound() { // check for sound file to be loaded and wanting to be player if (vars.saveSoundLoaded && vars.playSaveSound) { play(vars.saveSound, vars.volume, vars.volume, 1, 0, 1f); } }

When the user does select the menu button we can toggle the sounds on and off. If either of the sounds

are off we can set the variables to true if they are loaded and send a message to the user that the

sounds are on or restarted. In the case the sounds are on and then shut off we send the message that

the sounds are off. I separated the checks for sounds to be loaded since one could have failed during

loading. This allows is to know that if one of them was loaded we can turn them both off.

public void toggleSounds() { // assume turning sound off boolean soundOff = false; // check for sound loaded the change flag if (vars.saveSoundLoaded) { if (vars.playSaveSound) { vars.playSaveSound = false; soundOff = true; } }

// again check for sound to be loaded first if (vars.pageTurnLoaded) { if (vars.playPageTurnSound) { vars.playPageTurnSound = false; soundOff = true; } } // if either sound had to be turned off then skip here if (!soundOff) { if (vars.saveSoundLoaded) vars.playSaveSound = true; if (vars.pageTurnLoaded) vars.playPageTurnSound = true; work.showToast(vars.context, "Sounds On/Restarted"); } else { work.showToast(vars.context, "Sounds Off"); } }

NewImageSwitcherImage.java If this is your first experience with AsyncTask you will find it useful to perform actions behind the

scenes and then modify the user’s view in a seamless way. Further reading can be found here.

Essential Reading: http://developer.android.com/reference/android/os/AsyncTask.html

For this example we are using it to load bitmaps to the ImageSwitcher. We will need a WeakReference

to hold the ImageSwitcher itself and then we need the CommonVariables class. When we call the

constructor we will need the ImageSwitcher object that is being used to store a copy of it.

public NewImageSwitcherImage(ImageSwitcher imageSwitcher) { // set reference to the image switcher is = new WeakReference<ImageSwitcher>(imageSwitcher); }

The doInBackground method takes parameters for updating to progress of work to the user. For this

example I simply add code to get screen dimensions and begin decoding and creating the bitmap to be

set. I get the screen dimensions from the window manager using display metrics. Here I store them in

the CommonVariables instance and also use them to decode the bitmap and create a scaled bitmap.

Finally we turn the bitmap into a BitmapDrawable that can be set to the ImageSwitcher.

@Override protected BitmapDrawable doInBackground(Object... params) { WindowManager wm = (WindowManager) common.context .getSystemService(Context.WINDOW_SERVICE); DisplayMetrics metrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(metrics); common.screenH = metrics.heightPixels; common.screenW = metrics.widthPixels; Bitmap bitmap = decodeSampledBitmapFromResource(common.res, Data.PICS[common.currentImagePosition], common.screenW,

common.screenH); bitmap = Bitmap.createScaledBitmap(bitmap, common.screenW, common.screenH, true); BitmapDrawable bd = new BitmapDrawable(common.res, bitmap); return bd; }

You will want to review how Android handles bitmaps since a very large bitmap will cause a significant

delay to your application.

Essential Reading: http://developer.android.com/training/displaying-bitmaps/index.html

OnPostExecute we can then check for the ImageSwitcher to still exists and set the image drawable to it.

@Override protected void onPostExecute(BitmapDrawable result) { super.onPostExecute(result); if (is != null) { is.get().setImageDrawable(result); } } Most of this class was put together from the links provided and the source documentation for

AsyncTask. The methods calculateInSampleSize and decodeSampledBitmapFromResource are used

straight from the website documentation for loading large bitmaps.

Essential Reading: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

SavePhoto.java The SavePhoto class is used to do just what it says, save a photo to the user’s phone that will appear in

their Gallery. I used code from the Android pages here and only made minor changes such as the

naming convention, which is a concatenation the artists name and the image number. Read these pages

for further information on how I put it together.

Essential Reading: http://developer.android.com/training/basics/data-storage/files.html

Essential Reading: http://developer.android.com/guide/topics/data/data-storage.html#filesInternal

We create our thread from the context that we cast as an Activity to run the thread on the UI.

final Activity activity = (Activity) vars.context; activity.runOnUiThread(new Runnable() {…

We start out assuming that storage is not available and we cannot write to the phone.

private boolean mExternalStorageAvailable = false; private boolean mExternalStorageWritable = false; if (Environment.MEDIA_MOUNTED.equals(state)) { // We can read and write the media mExternalStorageAvailable = mExternalStorageWritable = true; } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { // We can only read the media

mExternalStorageAvailable = true; mExternalStorageWritable = false; } else { // Something else is wrong. It may be one of many other // states, but all we need // to know is we can neither read nor write mExternalStorageAvailable = mExternalStorageWritable = false; }

After we check that this is possible we can start to declare the name and path of the file’s location in the

device for saving. Next we check that the file doesn’t already exist.

if (mExternalStorageAvailable && mExternalStorageWritable) { // then write picture to phone File path =

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);

String name = vars.res.getString(R.string.artist) + "_" + currentImageToSave + ".jpeg";

File file = new File(path, name); InputStream is = null; // check for file in directory if (file.exists()) { work.showToast(vars.context, vars.res.getString(R.string.save_exists));

If we can make the directory or the directory exists we can begin to write to the device using

OutputStream.

boolean b1 = path.mkdirs(); oolean b2 = path.exists(); // Make sure the Pictures directory exists. if (b1 || b2) {

// get file into input stream is = activity.getResources().openRawResource( Data.PICS[currentImageToSave]);

OutputStream os = new FileOutputStream(file); byte[] data = new byte[is.available()]; is.read(data); os.write(data); is.close(); os.close();

work.showToast(vars.context, vars.res.getString(R.string.save_success));

At any point where the saving of the image might fail the CommonWork class is used to show a toast

message to the user. We also show the toast message on success of saving the image. Finally, the

MediaScannerConnection static method call to scanFile is used to refresh the device so that the image

will appear in the index of your phone or device.

MediaScannerConnection.scanFile( vars.context, new String[] { file.toString() }, null, new MediaScannerConnection.OnScanCompletedListener() { @Override public void onScanCompleted( String path, Uri uri) {} });

JUnit Testing Testing should be performed after you update the application and would like to not only prove it works

from a usual usage play testing perspective but also from the technical perspective.

The classes included are divided into three main classes a null object test class that tests that all essential

components are not null at the start of the application, there is a MediaPlayer test class, and a page turn

testing class that you can throttle the speed and number of times the pages turn to show new images.

To run all tests in succession for a defined number of times you use the Test suite method and call each

test class that you would like to add in. You can then run this in a loop for a specified number of times.

package radical.art.template; import junit.framework.Test; import junit.framework.TestSuite; public class AllTests { public static Test suite() { TestSuite suite = null; suite = new TestSuite(AllTests.class.getName()); for (int i = 0; i < 1; i++) { // $JUnit-BEGIN$ suite.addTestSuite(NonNullTests.class); suite.addTestSuite(MediaPlayerPuzzleTests.class); suite.addTestSuite(PageTurnTests.class); // $JUnit-END$ } return suite; } }

When I extend ActivityInstrumentationTestCase2 class to make test scripts the class is given a set of

common methods to be able to perform testing with, you can perform set up and tear down work. So

that when you start the application tests you can set the application in a particular state. Read the below

for a quick overview of these methods functionality.

Essential Reading:

http://developer.android.com/reference/android/test/ActivityInstrumentationTestCase2.html

I use a NonNullTests.java test class to test all the essential objects are in fact being created. It’s a simple

test class and a good starting point to see how to get a reference to your MainActivity instance and test

that it and other classes or objects are not null.

public void testImageSwitcherNotNull() { assertNotNull(mainActivity.artViewer.imageSwitcher); }

I also had some difficulty in creating the MediaPlayer before coding it to the state diagram from the

documentation. After wards I started writing the tests to reflect the functionality that I wanted the tests

to prove were working. For the MediaPlayer I include tests for the following.

1. Prove the media player starts on the start of the application.

a. You can test that the media player is playing by asserting the isPlaying() method of

the MediaPlayer is true.

if (!mainActivity.commonVariables.playMusic) { getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU); getInstrumentation().runOnMainSync(new Runnable() {@Override public void run() { mainActivity.menu.performIdentifierAction( R.id.menu_music_toggle, 0); } }); getInstrumentation().waitForIdleSync(); } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } assertTrue(mp.isPlaying());

II. Prove the menu button stops the MediaPlayer from playing.

b. At the start of the application pausing should take the MediaPlayer out of the started

state. You should run test without changing SharedPreferences.

public void testMediaPlayerMenuPlayingToPauseButtonToggleWorks() { getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU); getInstrumentation().runOnMainSync(new Runnable() {@Override public void run() { mainActivity.menu.performIdentifierAction( R.id.menu_music_toggle, 0); } }); getInstrumentation().waitForIdleSync(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace();

}

assertTrue(mainActivity.artViewer.myMediaPlayer.currentState != MyMediaPlayer.State.Started);

}

For the page turn methods, testLeftPageTurns and testRightPageTurns I use two variables TURNS

and SPEED to simulate the page turns of pressing the page turn buttons. You can change the settings of

each to find the threshold of your device for these actions. You can watch your device and see that the

pages are turning.

package radical.art.template; import android.test.ActivityInstrumentationTestCase2; public class PageTurnTests extends ActivityInstrumentationTestCase2 < MainActivity > { MainActivity mainActivity; public PageTurnTests() { super(MainActivity.class); } protected void setUp() throws Exception { super.setUp(); mainActivity = getActivity(); } protected void tearDown() throws Exception { super.tearDown(); } public void testRightTurn() { final int TURNS = 100; final int SPEED = 50; int before = mainActivity.cv.currentImagePosition; getInstrumentation().runOnMainSync(new Runnable() {@Override public void run() { mainActivity.RightArrowClick(null); } }); int actual = mainActivity.cv.currentImagePosition; assertTrue(actual != before); for (int i = 0; i < TURNS; i++) { try { Thread.sleep(SPEED); } catch (InterruptedException e) { e.printStackTrace(); }

before = mainActivity.cv.currentImagePosition; getInstrumentation().runOnMainSync(new Runnable() {@Override public void run() { mainActivity.RightArrowClick(null); } }); actual = mainActivity.cv.currentImagePosition; assertTrue(actual != before); } } public void testLeftPageTurns() { final int TURNS = 100; final int SPEED = 100; int before = mainActivity.cv.currentImagePosition; getInstrumentation().runOnMainSync(new Runnable() {@Override public void run() { mainActivity.LeftArrowClick(null); } }); int actual = mainActivity.cv.currentImagePosition; assertTrue(actual != before); for (int i = 0; i < TURNS; i++) { try { Thread.sleep(SPEED); } catch (InterruptedException e) { e.printStackTrace(); } before = mainActivity.cv.currentImagePosition; getInstrumentation().runOnMainSync(new Runnable() {@Override public void run() { mainActivity.LeftArrowClick(null); } }); actual = mainActivity.cv.currentImagePosition; assertTrue(actual != before); } } }

Attribution The code in this guide is free to use for commercial or non-commercial use however we assume no

responsibility for the use or misuse of the contents or that the code will run correctly in every situation

or on every device.

Richard A. Perez cannot be held responsible for inept or inappropriate use of source code or for failure

of source code to perform as expected for any reason.

Android Studio is a trademark of Google Inc.

Android, Google and Google Play are trademarks of Google Inc.

Portions of this pdf are reproduced from work created and shared by the Android Open Source Project and used

according to terms described in the Creative Commons 2.5 Attribution License.

Portions of this pdf are modifications based on work created and shared by the Android Open Source Project and

used according to terms described in the Creative Commons 2.5 Attribution License.

Errors, suggestions, problems implementing the code given here, or comments can be emailed to me

and I will try to address each as best I can.

Thanks,

-Rick

Richard A. Perez

[email protected]