Android: Puzzle Application - Games, Cosplay & Coding … · Android tries to keep your ......

65
Android: Puzzle Application Create an interactive puzzle from bitmaps. Richard Perez 12/31/16 Android 102

Transcript of Android: Puzzle Application - Games, Cosplay & Coding … · Android tries to keep your ......

Android: Puzzle Application Create an interactive puzzle from bitmaps.

Richard Perez 12/31/16 Android 102

Table of Contents Puzzle Introduction ....................................................................................................................................... 5

Android Concepts .......................................................................................................................................... 5

Android Lifecycle ....................................................................................................................................... 5

Fragment Lifecycle .................................................................................................................................... 6

Animations ................................................................................................................................................ 6

Music and Sounds ..................................................................................................................................... 6

Puzzle Code Overview ................................................................................................................................... 6

Bitmaps and Resizing ................................................................................................................................ 8

Puzzle Code ................................................................................................................................................... 9

MainActivity .............................................................................................................................................. 9

onCreate() ............................................................................................................................................. 9

switchToStatsFragment() .................................................................................................................... 11

updatePuzzleStats() ............................................................................................................................ 11

onActivityRestult ................................................................................................................................. 11

Puzzle Fragment ...................................................................................................................................... 12

NoisyAudioStreamReceiver ................................................................................................................ 12

onCreate() ........................................................................................................................................... 12

onCreateOptionsMenu() ..................................................................................................................... 13

onOptionsItemSelected() .................................................................................................................... 13

onStart() .............................................................................................................................................. 14

getSharedPrefs() ................................................................................................................................. 14

audioInit() ............................................................................................................................................ 16

referenceUIComponents() .................................................................................................................. 16

onCreateView() ................................................................................................................................... 17

puzzle_layout.xml ............................................................................................................................... 17

onResume() ......................................................................................................................................... 19

onPause() ............................................................................................................................................ 19

onStop()............................................................................................................................................... 20

onDestroy() ......................................................................................................................................... 21

PuzzleSurface.java ................................................................................................................................... 21

PuzzleSurface() .................................................................................................................................... 21

onWindowFocusChanged() ................................................................................................................. 22

surfaceChanged() ................................................................................................................................ 23

SurfaceCreated() ................................................................................................................................. 23

surfaceDestroyed().............................................................................................................................. 23

performClick() ..................................................................................................................................... 23

OnTouchEvent() .................................................................................................................................. 24

createNewSizedPuzzle() ...................................................................................................................... 24

createPuzzle() ...................................................................................................................................... 24

resumePuzzle() .................................................................................................................................... 25

musicActivity()..................................................................................................................................... 25

ma009Activity() ................................................................................................................................... 25

instagramActivity() .............................................................................................................................. 26

blogActivity() ....................................................................................................................................... 26

cleanup() ............................................................................................................................................. 27

getSlotString() ..................................................................................................................................... 27

newPuzzle() ......................................................................................................................................... 27

toggleMusic() ...................................................................................................................................... 28

toggleBorder() ..................................................................................................................................... 28

toggleWinSound() ............................................................................................................................... 29

toggleSetSound() ................................................................................................................................. 29

saveMusic() ......................................................................................................................................... 29

onPause() ............................................................................................................................................ 29

PuzzleUpdateAndDraw ........................................................................................................................... 30

PuzzleUpdateAndDraw() ..................................................................................................................... 30

updateAndDraw() ............................................................................................................................... 30

getSurfaceHolder() .............................................................................................................................. 30

updatePhysics() ................................................................................................................................... 30

doDraw() ............................................................................................................................................. 31

drawImage() ........................................................................................................................................ 31

drawImageWithMovingPiece() ........................................................................................................... 32

surfaceChanged() ................................................................................................................................ 33

pause() ................................................................................................................................................ 34

AdjustablePuzzle.java ............................................................................................................................. 34

getPreviousImageLoadedScaledDivided() .......................................................................................... 34

getNewImageLoadedScaledDivided() ................................................................................................. 35

divideBitmap() ..................................................................................................................................... 36

assignXandYtoBorderPointIndex() ...................................................................................................... 37

setBorderPoint() .................................................................................................................................. 37

divideBitmapFromPreviousPuzzle() .................................................................................................... 38

setPointsToSlotAndPiece() .................................................................................................................. 38

setBitmapToPiece() ............................................................................................................................. 38

updateAndDraw() ............................................................................................................................... 39

OnTouchEvent() .................................................................................................................................. 39

incrementPuzzleSolvedByPuzzleSize() ................................................................................................ 42

recycleAll() .......................................................................................................................................... 43

addTimeToTimer() .............................................................................................................................. 43

compareRecordTime() ........................................................................................................................ 43

resetTimer() ........................................................................................................................................ 44

getPuzzleTime() ................................................................................................................................... 44

onPause() ............................................................................................................................................ 44

initPieces() ........................................................................................................................................... 44

checkToBeSolved() .............................................................................................................................. 45

CommonVariables ................................................................................................................................... 45

getInstance() ....................................................................................................................................... 45

CommonVariables() ............................................................................................................................ 46

setSlots() ............................................................................................................................................. 46

assignSlotOrder() ................................................................................................................................ 46

calculateInSampleSize() ...................................................................................................................... 47

decodeSampledBitmapFromResource() ............................................................................................. 47

sendPieceToNewSlot() ........................................................................................................................ 48

showToast() ......................................................................................................................................... 48

playSetSound() .................................................................................................................................... 48

initPrevDivideBitmap() ........................................................................................................................ 49

initDivideBitmap() ............................................................................................................................... 49

hideButtons() ...................................................................................................................................... 50

toggleUIOverlay() ................................................................................................................................ 50

showButtons() ..................................................................................................................................... 52

Data.java ................................................................................................................................................. 52

PuzzleStatsFragment ............................................................................................................................... 53

newInstance() ..................................................................................................................................... 53

onCreate() ........................................................................................................................................... 53

onCreateOptionsMenu() ..................................................................................................................... 54

onCreateView() ................................................................................................................................... 54

onResume() ......................................................................................................................................... 55

updatePuzzleStats() ............................................................................................................................ 55

SaveMusicService .................................................................................................................................... 56

SaveMusicService() ............................................................................................................................. 57

onHandleIntent()................................................................................................................................. 57

serviceToast() ...................................................................................................................................... 57

saveMusicTrack() ................................................................................................................................ 57

PuzzleSlot ................................................................................................................................................ 59

PuzzlePiece .............................................................................................................................................. 60

Test Code .................................................................................................................................................... 60

ThreadingTests ........................................................................................................................................ 60

setUp()................................................................................................................................................. 60

NotNullTests ........................................................................................................................................ 61

testMenuPuzzleRecreate() .................................................................................................................. 61

testMenuPuzzleRecreateAndCloseDefaultCreate() ............................................................................ 62

RandomPuzzleTest .................................................................................................................................. 62

testMenuPuzzleRecreateAndCloseDefaultCreate .............................................................................. 62

Attribution................................................................................................................................................... 63

Puzzle Introduction The puzzle application for Android™ is designed to use images from the developer and divide them into

sections. The algorithm will disperse the pieces as randomly as possible into different slots on screen.

Reassembly of the pieces will give the user the option to save the image to their device. After solving the

puzzle, the user will also be able to tap the screen to see the full image without anything cluttering the

UI (user interface). I have added link buttons which you can modify to direct your audience to any

website you like. I try to make it the artist’s blogs or my blog where the users can see more artworks or

learn to make their own applications.

I have named the classes of the application to help you to understand how the puzzle applications works

as well as how to modify it when you are comfortable with the code base. I have also included some

test scripts to help make the application run better and make sure its essential features are working.

I have a couple of other guides that might be a little bit lighter in scope, but they can help you get

started if you find that this one is too broad in scope for you. They include full explanations of the code

as well as give you access to the full code base. You can find the guides here and the code base here…

Guides: http://www.mobileapplications009.com/category/coding-pdf/

!! Download Updated Code Base With Save Feature Updates for devices Marshmallow+ !!

Code Base: https://github.com/mobileapplications009

Have fun and please feel free to contact me if you find any mistakes and I will update the applications

and code base as needed.

Email: [email protected]

Android Concepts This section will cover topics that you can find more information on regarding development that are

essential to learning and understanding how the Android OS works. You will want to start at the official

developer websites provided by Google as well as download samples and review the code they provide.

https://developer.android.com/index.html

While Google does provide many examples you will also find the answer to common question’s at sites

such as Stack Overflow. There is a large amount of information that is useful and moderated by other

developers there.

http://stackoverflow.com/

Android Lifecycle The Android Lifecycle can be seen as the methods pertaining to the opening and closing of the

application. The device itself may call these methods while the application is in the background.

https://developer.android.com/training/basics/activity-lifecycle/index.html

You will want to make sure you resume your application correctly and try to recreate the same

experience for the user as when they left it. Android tries to keep your application in a paused state for

resuming, however due to memory limitations the system may shut down your application in the

background. At this time you will need to handle resuming by recreating the state yourself.

Fragment Lifecycle Android allows for flexibility with layouts by providing Fragments that can be used in combination to

create a layout. You can declare the kind of layout you want depending on the size and type of the

user’s device. This also allows for compartmentalization of multiple Fragments depending on the screen

real estate of the device. The Fragment lifecycle is more likely to go through being destroyed where

Activities will be kept alive in the device as you pause the application.

https://developer.android.com/guide/components/fragments.html

If Fragments are a little bit complicated you can start using Activities only in your beginning

developments and once you understand them you can start porting them into Fragments. You will find

this advantageous over time as your layouts become more complex and you add features.

Animations As you have your UI developed you will find that adding animations are the best way to give your

application a little bit more visual appeal. Android provides many animations out of the box and you can

read more about creating animations in the following pages.

https://developer.android.com/training/animation/index.html

Creating simple animations for transitions or during layout changes will keep the user interested in using

your application. They can give your applications an extra level of polish that also makes the applications

a little bit more fun to use by providing some eye candy to the user.

Music and Sounds The Android MediaPlayer code I provide is meant to show one way to implement providing music to the

user while they use the application.

https://developer.android.com/guide/topics/media/mediaplayer.html

I followed the MediaPlayer state guide from the above link, to give the user the flexibility to have music

or to turn the default track off and have their own music play while the application is running.

While MediaPlayer is meant for music, the quick sound effects meant for rapid succession, like boom

effects or quick tap sounds, use the SoundPool class.

https://developer.android.com/reference/android/media/SoundPool.html

I use the SoundPool class in this application when the pieces are being moved or the image is being

saved. SoundPool provides a way to create simple quick sounds without the large coding overhead of

the MediaPlayer. Also the MediaPlayer can use a lot of resources so you will want to make sure to not

overload the user’s phone CPU, needlessly running multiple MediaPlayer instances.

Puzzle Code Overview What I have happening behind the scenes is a set of slots that represent the image divided into the

number of pieces you choose. In the images below I have a 2 x 2 sized puzzle for 4 slots, remember

arrays use 0 as the starting index in the array. These slots are outlined in red in the pic on the right. Each

slot knows its own number as well as it knows its corner’s pixel coordinates relative to the screen.

Android uses a coordinate grid but the origin (0, 0) is in the top left corner. I allow each piece to be

moved freely but on release of the piece it will either return to its original slot using, its own slot’s

corner coordinates to place it or it will switch with another slot’s piece, in this case all four corners of

the puzzle piece are updated with the new slots hard coded coordinates.

The next images show the outline of the pieces marked in yellow, you can tell that piece number is out

of place from the slots, for instance in the top left slot we have piece 2 in slot 0. You can then see that as

a piece is switched with another the slot numbers always stay the same, notice the red is always in place

and the yellow numbers are out of order. When a piece moves to a new slot it we can easily find if the

puzzle is solved or not. Performing a check to see that every puzzle piece’s number matches the slot it is

located in, determines if the puzzle is solved or not.

Bitmaps and Resizing Android devices generally have powerful CPU’s and can store a lot of files however there is one area

that is still catching up to the point of excess and that is in the system memory. Loading large bitmaps

can crash an application very quickly if not done correctly. This involves reading the bitmap in and then

scaling it to the phone’s resolution while still providing a good quality image.

https://developer.android.com/training/displaying-bitmaps/index.html

https://developer.android.com/training/displaying-bitmaps/manage-memory.html

You will want to read the samples given to be able to provide nice images to the user without draining

their device of memory. Most of the work involves running code off of the main UI thread by using

background threads. If you aren’t using extremely large bitmaps that will always need to be scaled to a

large degree, you won’t make the user wait too long for scaling operations to complete.

Puzzle Code The puzzle code I have written uses a divide and disperse algorithm that takes an image and splits it into

sections. Android has a bitmap class that allows for manipulating an image into sub-sections, this is

helpful for dividing the original image into sections. I have refactored the application to use Fragments

and I try to accommodate different screen sizes as well as device types through different xml layouts. In

the code you will find background threads for operations like updating the UI and saving music or

images.

MainActivity The MainActivity class is the starting point to the application. The MainActivity will have communication

with both our PuzzleFragment and our PuzzleStatsFragment depending on which layout file the device

best supports. On tablet, we will be able to communicate directly with both of the Fragments, while on

mobile we will have to use transactions to switch the Fragments.

onCreate() In the onCreate method I set the contents view by declaring a fragment(s) in the layout file. We do this

at runtime when we create a new PlaceholderFragment and commit this using the support Fragment

Manager. We are attaching the Fragment to the container that is in the activity_main.xml file.

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.fragment_main);

}

Here is a layout with one Fragment…

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:id="@+id/fragment_container"

android:layout_height="match_parent" >

<fragment

android:id="@+id/puzzleFragment"

android:name="puzzle.template.puzzle.PuzzleFragment"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:tag="puzzle.template.puzzle.PuzzleFragment"

tools:layout="@layout/puzzle_layout" />

</RelativeLayout>

And here is another where the layout features two Fragments…

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:baselineAligned="false"

android:orientation="horizontal">

<fragment

android:id="@+id/puzzleFragment"

android:name="puzzle.template.puzzle.PuzzleFragment"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_weight="1"

android:tag="puzzle.template.puzzle.PuzzleFragment"

tools:layout="@layout/puzzle_layout" />

<fragment

android:id="@+id/puzzleStatsFragment"

android:name="puzzle.template.puzzle.PuzzleStatsFragment"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_weight="4"

android:tag="puzzle.template.puzzle.PuzzleStatsFragment"

tools:layout="@layout/fragment_puzzle_stats" />

</LinearLayout>

Inside of the layout inflater is the fragment_main.xml file, however to determine the kind of layout that

the device can best support I include a few different kinds.

To support tablets I supply a few different layout types that will be applied for tablets.

Large

Large-land

sw600dp

sw600dp-land

This attempts to account for different screen sizes, in general using sw600dp will catch most tablet sizes

but adding layout for large will help to catch any other’s that are tablets but use lower resolutions. You

should create new layout resource files and set different attributes for different device types in order to

provide the best user experience you can.

switchToStatsFragment() If the device is mobile it will use one fragment in each activity. You can see the stats of the puzzles you

have solved by pressing the + icon. Here the menu will run this code to check for the stats fragment to

exist, this is possible if the stats fragment is already open. If it’s in the stats fragment, pop the fragment

from the stack to return to the puzzle application. Otherwise it will open the puzzle stats fragment and

place the fragment on the stack so it can return to the puzzle fragment.

private void switchToStatsFragment() {

PuzzleStatsFragment fragment = (PuzzleStatsFragment)

getSupportFragmentManager().findFragmentByTag(PuzzleStatsFragment.TAG);

if (fragment == null) {

//start the stats fragment

getSupportFragmentManager().beginTransaction()

.add(R.id.fragment_container, PuzzleStatsFragment.newInstance(),

PuzzleStatsFragment.TAG).addToBackStack("puzzle").commit();

} else {

getSupportFragmentManager().popBackStack();

}

}

Follow the link to see more information on providing navigation between Activities and Fragments.

https://developer.android.com/training/implementing-navigation/temporal.html

updatePuzzleStats() This method will be called when a puzzle is solved, it will update all of the stats in the saved variables as

well as update the stats fragment if it is visible to the users. This will be the case in tablet mode.

public void updatePuzzleStats() {

PuzzleStatsFragment fragment = (PuzzleStatsFragment)

getSupportFragmentManager().findFragmentByTag(PuzzleStatsFragment.TAG);

if (fragment != null) {

// Call a method in the ArticleFragment to update its content

fragment.updatePuzzleStats();

}

}

onActivityRestult When the save music track service has completed it will communicate with the activity in this callback.

We check for the return code to match our request of save music and from there we have success or

failure to handle.

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

super.onActivityResult(requestCode, resultCode, data);

if (requestCode == SaveMusicService.SAVE_MUSIC_REQUEST_CODE) {

switch (resultCode) {

case SaveMusicService.ERROR:

break;

case SaveMusicService.SUCCESS:

updatePuzzleStats();

break;

}

}

}

Puzzle Fragment The puzzle Fragment is set as part of the layout on tablet or the whole layout in case of mobile. This will

hold the surface our puzzle pieces will be manipulated on, as well as the sound and music classes, these

will also make use of the NoisyAudioStreamReciever class that handles if the headphones should

become unplugged. We keep track of items like the common variables used in different classes from this

Singleton class A Singleton is something you should read more about if you are new to it, it is a class that

is created one time and multiple classes can access its variables. It’s great for games that have one set of

stored variables like high score’s or current health that carries over across different levels.

NoisyAudioStreamReceiver This a class that extend Broadcast Receiver. When the event that the receiver is meant for matches its

calling action, in this case it’s the Action Audio Becoming Noisy, its code will run. This should catch when

the headphones become unplugged, we handle it by turning off the audio and the user will have to

manually turn it back on after plugging in their headphones again.

https://developer.android.com/reference/android/content/BroadcastReceiver.html

public class NoisyAudioStreamReceiver extends BroadcastReceiver {

@Override

public void onReceive(Context context, Intent intent) {

if (common.isLogging)

Log.d(LOG_TAG, "onReceive NoisyAudioStreamReceiver");

if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent

.getAction())) {

// quiet the media player

if (myMediaPlayer != null) {

if (common.playMusic) {

common.playMusic = false;

myMediaPlayer.pause();

common.showToast(context, "Music Off");

}

}

}

}

}

We will have to call startPlayback in the onResume method and call stopPlayback in the onPause

method, not doing so will not activate its listener if unset and cause the app to crash if not released.

onCreate() The onCreate method is called when the application is ready to begin creating the Puzzle Fragment

class. Here will tell the device that this Fragment will have options in its menu and keep the fragment in

memory on closing.

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setHasOptionsMenu(true);

setRetainInstance(true);

}

onCreateOptionsMenu() Now that we can create the menu, we do this when the OnCreateOptionsMenu method. Inflate the

layout from the main_puzzle.xml file. I keep a reference to the menu for testing purposes.

@Override

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {

inflater.inflate(R.menu.main_puzzle, menu);

super.onCreateOptionsMenu(menu, inflater);

this.menu = menu;

}

onOptionsItemSelected() Now that the menu is going to be built we will need to add a listener so that we can handle all the

actions of the button presses. All of the methods will call to the PuzzleSurface class to handle the events

such as creating a new puzzle, and toggling sound effects. We can also run a service that will save the

music track included in the application to the user’s device. If you are new to what a Service is in

Android, it runs code in the background to completion and can even keep running code after the user

has left the application. You can also run the MediaPlayer from a Service if using the code is

troublesome for you.

@Override

public boolean onOptionsItemSelected(MenuItem item) {

switch (item.getItemId()) {

case R.id.new_puzzle:

puzzleSurface.newPuzzle();

return true;

case R.id.music_toggle:

puzzleSurface.toggleMusic();

return true;

case R.id.set_toggle:

puzzleSurface.toggleSetSound();

return true;

case R.id.win_toggle:

puzzleSurface.toggleWinSound();

return true;

case R.id.border_toggle:

puzzleSurface.toggleBorder();

return true;

case R.id.save_music:

puzzleSurface.saveMusic();

return true;

}

return super.onOptionsItemSelected(item);

}

onStart() When the app is near ready to start and just before resuming we have the on start method that we use

here to get the shared preferences of the application. These are variables that are saved in the device.

While this is useful for saving the application state they are limited to saving one state of the application

in practical use. If you have a game with several states you may want to consider using a database for

handling several states.

@Override

public void onStart() {

super.onStart();

getSharedPrefs();

}

getSharedPrefs() In this lengthy method we have to iterate over every saved state variable that there is to make the

applications resume for the user exactly the way they left it. If you find it tedious to implement this

method and are tempted to simply refresh the app from the beginning, know that that is similar to not

having save slots in a game for the user. While I don’t mind typing in my home address in Maps again if

needed, replaying a game to the final level just because I had to stop for dinner causes a lot of

frustration and will cost you user installs over time.

private void getSharedPrefs() {

if (common.isLogging)

Log.d(LOG_TAG, "getSharedPrefs PuzzleFragment");

sharedpreferences = getActivity().getSharedPreferences(

getString(R.string.MY_PREFERENCES), Context.MODE_PRIVATE);

I keep an isValid variable to tell if the saved state is in-tact while reloading variables.

// check for all to be loaded here

boolean isValid = false;

In recovering the current image number to use in recreating the puzzle I check for the variable to exist. I

also check that the number is valid. I add a default value of 0 when attempting to get the stored

“ImageNumber” variable, in case it is corrupt or deleted, this will help keep the app running in case of a

failure during recovering the state. If the state or the variable is not in acceptable range I also set the

isValid boolean variable as false so it won’t try to rebuild an incorrect puzzle state.

int posImage = 0;

if (sharedpreferences.contains(getString(R.string.IMAGENUMBER))) {

posImage = sharedpreferences.getInt(

getString(R.string.IMAGENUMBER), 0);

if (posImage >= 0 || posImage < Data.PICS.length) {

isValid = true;

}

} else {

isValid = false;

}

Recovering the saved slots is done from a string of comma separated integers. I don’t have one set

amount of numbers of slots for the pieces to fit into. In this case I check that the number of slots is set to

the sum of the pieces in numerical addition. If we have a two by two piece puzzle the slots are

numbered from 1 to 4 so this number at the end must equal 0 + 1 + 2 + 3 and this turns out to be 8. So

we must have four slots whose numbers equal 8. When I read in the values of the slots saved they

should also equal 8. If not the slot list is invalid for the puzzle size. We can also tell the size of the puzzle

by taking the square root of the number of slots, if we have four slots then we have a 2 x 2 size puzzle.

String slots = "";

if (isValid) {

if (sharedpreferences.contains(getString(R.string.SLOTS))) {

slots = sharedpreferences.getString(getString(R.string.SLOTS), "");

if (slots.equals("") || slots.length() < 2) {

isValid = false;

} else {

String[] slotArr = slots.split(",");

int expectedTotal = sumToPositiveN(slotArr.length - 1);

int actualTotal = 0;

for (String aSlotArr : slotArr) {

try {

int temp = Integer.parseInt(aSlotArr);

actualTotal += temp;

} catch (NumberFormatException nfe) {

isValid = false;

}

}

common.dimensions = Math.sqrt((double) slotArr.length);

if (expectedTotal != actualTotal) {

isValid = false;

}

}

} else {

isValid = false;

}

}

This code might seem complex at first since we doing a few checks but it will help to validate the puzzle

state can be restored correctly. After checking all variables needed for the puzzle state we should have a

valid or invalid puzzle, if the puzzle is valid we can set the variables to our common variables class and if

not we will create a new puzzle and silently apologize to anyone solving a 7 x 7 puzzle and has their save

data corrupted. I have tried to solve a 49 piece puzzle till there was one swap left and then tested the

shared preferences will work to restore the app as you left it to put the last pieces in place. No user

should have their puzzle lost at this point… pure frustration!

if (isValid) {

common.currentPuzzleImagePosition = posImage;

common.currentSoundPosition = posSound;

common.drawBorders = drawBorders;

common.playMusic = playMusic;

common.playChimeSound = playChime;

common.playTapSound = playTap;

common.setSlots(slots);

common.resumePreviousPuzzle = true;

common.currPuzzleTime = currentTime;

} else {

common.createNewPuzzle = true;

}

I also separated the menu state options such as music on and off and saved stats for your puzzles this

way if the stats are corrupted you at least won’t lose the last puzzle you were working on.

//start of saved stats

if (sharedpreferences.contains(getString(R.string.PUZZLES_SOLVED))) {

common.puzzlesSolved =

sharedpreferences.getInt(getString(R.string.PUZZLES_SOLVED), 0);

}

audioInit() Here we can initialize our sound players for music and sound effects as well as setting them for

availability within our classes for later use.

private void audioInit() {

// create new my media player

myMediaPlayer = new MyMediaPlayer(getContext());

myMediaPlayer.init();

puzzleSurface.myMediaPlayer = myMediaPlayer;

mySoundPool = new MySoundPool(15, AudioManager.STREAM_MUSIC, 100);

mySoundPool.init(getContext());

common.mySoundPool = mySoundPool;

}

referenceUIComponents() Each view object must be captured in a reference and added to our common variables for use later

during puzzle solving and user interaction. We can also declare the click listeners here.

private void referenceUIComponents(View view) {

// The UI has a puzzle

puzzleSurface = (PuzzleSurface) view.findViewById(R.id.puzzle);

common.mNextButton = ((Button) view.findViewById(R.id.nextButton));

common.mNextButton.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

puzzleSurface.nextImage();

}

});

common.musicButton = ((ImageButton) view

.findViewById(R.id.musicButton));

common.musicButton.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

puzzleSurface.musicActivity();

}

});

common.blogButton = ((ImageButton) view

.findViewById(R.id.blogButton));

common.blogButton.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

puzzleSurface.blogActivity();

}

});

common.ma009Button = ((ImageButton) view.findViewById(R.id.ma009Button));

common.ma009Button.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

puzzleSurface.ma009Activity();

}

});

common.instagramButton = ((ImageButton)

view.findViewById(R.id.instagramButton));

common.instagramButton.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

puzzleSurface.instagramActivity();

}

});

}

onCreateView() Using a puzzle layout file we can set up where the buttons and main surface view will be.

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedInstanceState) {

final View view = inflater.inflate(R.layout.puzzle_layout, container,

false);

referenceUIComponents(view);

audioInit();

return view;

}

puzzle_layout.xml The layout file is an xml file can directly calls to the puzzle surface class as well as places button that will

appear on solve of each puzzle.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context=".MainActivity">

<puzzle.template.puzzle.PuzzleSurface

android:id="@+id/puzzle"

android:layout_width="fill_parent"

android:layout_height="fill_parent" />

<Button

android:id="@+id/nextButton"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerInParent="true"

android:background="@drawable/buttonstyle"

android:contentDescription="@string/continue_desc"

android:text="@string/next_image"

android:visibility="invisible" />

<ImageButton

android:id="@+id/musicButton"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentLeft="true"

android:layout_alignParentStart="true"

android:layout_alignParentTop="true"

android:contentDescription="@string/music_link_desc"

android:src="@mipmap/fma"

android:visibility="invisible" />

<ImageButton

android:id="@+id/blogButton"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentEnd="true"

android:layout_alignParentRight="true"

android:layout_alignParentTop="true"

android:contentDescription="@string/blog_link_button"

android:src="@mipmap/deviant_art"

android:visibility="invisible" />

<ImageButton

android:id="@+id/ma009Button"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:contentDescription="@string/blog_link_button"

android:src="@mipmap/ma009"

android:visibility="invisible"

android:layout_alignParentBottom="true"

android:layout_alignLeft="@+id/blogButton"

android:layout_alignStart="@+id/blogButton" />

<ImageButton

android:id="@+id/instagramButton"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:contentDescription="@string/instagram_link_button"

android:src="@mipmap/instagram"

android:visibility="invisible"

android:layout_alignParentBottom="true"

android:layout_alignParentLeft="true"

android:layout_alignParentStart="true" />

</RelativeLayout>

onResume() In resuming our application we can set the volume based on the current user’s settings. We will need

the audio manager to tell your device we will be using the audio stream. We can release this if we want

the user to play music from their own library.

We also start listening for the headphones to become unplugged by starting our noisy audio stream

receiver. We also only start the MediaPlayer if the settings are set to play music.

@Override

public void onResume() {

super.onResume();

AudioManager audioManager = (AudioManager) getActivity()

.getSystemService(Context.AUDIO_SERVICE);

float streamVolume = (float) audioManager

.getStreamVolume(AudioManager.STREAM_MUSIC);

common.volume = streamVolume

/ (float) audioManager

.getStreamMaxVolume(AudioManager.STREAM_MUSIC);

noisyAudioStreamReceiver = new NoisyAudioStreamReceiver();

startPlayback();

if (common.playMusic) {

int posSound = 0;

sharedpreferences = getActivity().getSharedPreferences(

getString(R.string.MY_PREFERENCES), Context.MODE_PRIVATE);

if (sharedpreferences.contains(getString(R.string.POSITION))) {

posSound = sharedpreferences.getInt(getString(R.string.POSITION), 0);

}

common.currentSoundPosition = posSound;

myMediaPlayer.resume();

} else {

//return sound to device

myMediaPlayer.abandonFocus();

}

}

onPause() The on pause method is where we can stop the noisy audio stream listener and pause the MediaPlayer.

We also pause the puzzle surface which will allow us to be ready for saving the state in case the

application moves to onStop. If the app is paused and resumed quickly enough from the background we

won’t get to the onStop method.

@Override

public void onPause() {

super.onPause();

stopPlayback();

if (myMediaPlayer != null) {

myMediaPlayer.pause();

}

if (puzzleSurface != null)

puzzleSurface.onPause();

}

onStop() On stop will handle saving our game state to the local file called shared preferences. Aside from using

shared preferences you can save the state and even multiple states in a database.

To save the puzzle application’s state we will need to record every aspect of the current puzzle and then

record the state of the settings of the application. We will also save the stats for the user for our puzzles

solved and interactions such as saved images and record time for solving each puzzle size.

@Override

public void onStop() {

super.onStop();

myMediaPlayer.onStop();

String slotString = puzzleSurface.getSlotString();

Long dateLong = common.currPuzzleTime;

Editor editor = sharedpreferences.edit();

editor.putInt(getString(R.string.IMAGENUMBER),

common.currentPuzzleImagePosition);

editor.putString(getString(R.string.SLOTS), slotString);

editor.putBoolean(getString(R.string.SOUND), common.playTapSound);

editor.putBoolean(getString(R.string.MUSIC), common.playMusic);

editor.putBoolean(getString(R.string.CHIME), common.playChimeSound);

editor.putBoolean(getString(R.string.BORDER), common.drawBorders);

editor.putInt(getString(R.string.POSITION), common.currentSoundPosition);

editor.putLong(getString(R.string.TIME), dateLong);

editor.putInt(getString(R.string.PUZZLES_SOLVED), common.puzzlesSolved);

editor.putInt(getString(R.string.IMAGES_SAVED), common.imagesSaved);

editor.putInt(getString(R.string.MUSIC_SAVED), common.musicSaved);

editor.putInt(getString(R.string.BLOG_LINKS_TRAVERSED),

common.blogLinksTraversed);

editor.putInt(getString(R.string.TWO_SOLVE_COUNT),

common.fourPiecePuzzleSolvedCount);

editor.putLong(getString(R.string.TWO_SOLVE_TIME),

common.fourRecordSolveTime);

editor.putInt(getString(R.string.THREE_SOLVE_COUNT),

common.ninePiecePuzzleSolvedCount);

editor.putLong(getString(R.string.THREE_SOLVE_TIME),

common.nineRecordSolveTime);

editor.putInt(getString(R.string.FOUR_SOLVE_COUNT),

common.sixteenPiecePuzzleSolvedCount);

editor.putLong(getString(R.string.FOUR_SOLVE_TIME),

common.sixteenRecordSolveTime);

editor.putInt(getString(R.string.FIVE_SOLVE_COUNT),

common.twentyfivePiecePuzzleSolvedCount);

editor.putLong(getString(R.string.FIVE_SOLVE_TIME),

common.twentyFiveRecordSolveTime);

editor.putInt(getString(R.string.SIX_SOLVE_COUNT),

common.thirtysixPiecePuzzleSolvedCount);

editor.putLong(getString(R.string.SIX_SOLVE_TIME),

common.thirtySixRecordsSolveTime);

editor.putInt(getString(R.string.SEVEN_SOLVE_COUNT),

common.fourtyninePiecePuzzleSolvedCount);

editor.putLong(getString(R.string.SEVEN_SOLVE_TIME),

common.fourtyNineRecordsSolveTime);

editor.apply();

}

When we save the slots to a string we will put all slots into one string that is separated by commas. This

will represent the number of pieces in the current puzzle and the location of each piece.

onDestroy() The last method called in our activity is the on destroy method. This is where we need to free resources

for the device and clean up any remaining bitmaps that can also use up valuable memory.

@Override

public void onDestroy() {

super.onDestroy();

if (mySoundPool != null) {

mySoundPool.release();

mySoundPool = null;

}

if (myMediaPlayer != null) {

myMediaPlayer.cleanUp();

myMediaPlayer = null;

}

if (puzzleSurface != null) {

puzzleSurface.cleanUp();

puzzleSurface = null;

}

}

PuzzleSurface.java The puzzle surface is the sheet of pixels that we will draw our bitmaps on. In this class we will keep a

reference to the MediaPlayer and common variables. We also have an abstraction of the puzzle itself in

the AdjustablePuzzle class, this will perform the logic of divide and disperse for the puzzle pieces. We

also have a PuzzleUpdateAndDraw class this will handle the updating of puzzle pieces on touch and

move of a piece as well as the drawing of the pieces to the canvas. We extend the SurfaceView class

which contains the drawing surface for us. We need to adjust variables based on the type of surface our

phone is given and this can be handled after implementing the SurfaceHolder.Callback class here as well.

PuzzleSurface() Our constructor for the puzzle surface starts to do the work of setting variables that will be used

throughout the class as well as set up our SurfaceHolder and adding our callback when the surface is

ready for drawing on. The surface holder will allow us to set the size if we want to, however in this

application we are using the full screen so we don’t set very much, instead we pull variables from it.

It is important to note that even though we aren’t using the attrs field in our code these parameters are

required so that we can declare this class in the xml and it will automatically call the constructor. If you

notice we don’t call new PuzzleSurface() in our code anywhere, this is because when we declare this

view in the xml with these parameters it will automatically start it up for us.

public PuzzleSurface(Context context, AttributeSet attrs) {

super(context, attrs);

// set context for access in other classes

this.context = context;

common.res = context.getResources();

puzzleSurface = this;

// register our interest in hearing about changes to our surface

SurfaceHolder holder = getHolder();

holder.addCallback(this);

borderPaintA = new Paint();

borderPaintA.setStyle(Paint.Style.STROKE);

borderPaintA.setStrokeWidth(STROKE_VALUE);

borderPaintA.setColor(Color.LTGRAY);

borderPaintA.setAlpha(TRANS_VALUE);

borderPaintB = new Paint();

borderPaintB.setStyle(Paint.Style.STROKE);

borderPaintB.setStrokeWidth(STROKE_VALUE);

borderPaintB.setColor(Color.DKGRAY);

borderPaintB.setAlpha(TRANS_VALUE);

transPaint = new Paint();

transPaint.setAlpha(TRANS_VALUE);

transPaint.setStyle(Paint.Style.FILL);

fullPaint = new Paint();

puzzleUpdateAndDraw = new PuzzleUpdateAndDraw(holder, context);

}

onWindowFocusChanged() In this method we are told when the window has the view and is ready for interactions. If we use this in

our on touch methods we can stop the user from breaking our app by touching the screen before its

ready and running code for the puzzle before it’s ready to. We also perform our first update and draw

since the puzzle should be ready at this point to show to the user.

@Override

public void onWindowFocusChanged(boolean hasWindowFocus) {

if (common.isLogging)

Log.d(TAG, "onWindowFocusChanged PuzzleSurface hasWindowFocus:" +

hasWindowFocus);

if (!hasWindowFocus) {

common.isWindowInFocus = false;

} else {

common.isWindowInFocus = true;

puzzleUpdateAndDraw.updateAndDraw();

}

}

surfaceChanged() A surface holder callback that gives us the size of the surface created that is unique to our device, we

can store these variables and use them to create or resume our puzzle based on saved state.

public void surfaceChanged(SurfaceHolder holder, int format, int width,

int height) {

if (common.isLogging)

Log.d(TAG, "surfaceChanged PuzzleSurface " + width + " " + height);

puzzleUpdateAndDraw.surfaceChanged(width, height);

if (common.resumePreviousPuzzle) {

common.resumePreviousPuzzle = false;

resumePuzzle();

} else if (common.createNewPuzzle) {

common.createNewPuzzle = false;

createPuzzle();

}

}

SurfaceCreated() Another surface holder callback that we use to store our holder and context in the puzzle update and

draw method. This will be used in drawing to lock the canvas and then refresh our new puzzle state in its

place.

public void surfaceCreated(SurfaceHolder holder) {

if (common.isLogging)

Log.d(TAG, "surfaceCreated PuzzleSurface");

puzzleUpdateAndDraw = new PuzzleUpdateAndDraw(holder, context);

}

surfaceDestroyed() In our final callback I only added a log statement so that we can debug and find where the application is

running correctly or not.

public void surfaceDestroyed(SurfaceHolder holder) {

if (common.isLogging)

Log.d(TAG, "surfaceDestroyed PuzzleSurface");

}

performClick() We can allow Android to perform accessibility functions like reading text from content descriptions by

making a call to the super.performClick class.

@Override

public boolean performClick() {

if (common.isLogging)

Log.d(TAG, "performClick PuzzleSurface");

super.performClick();

return true;

}

OnTouchEvent() We handle our touch events at this level by sending most of our events to the adjustable puzzle class,

however if we are in the solved puzzle state and the puzzle is ready for interactions we can toggle the UI

widgets on and off for full screen viewing.

@Override

public boolean onTouchEvent(MotionEvent event) {

synchronized (puzzleUpdateAndDraw.getSurfaceHolder()) {

if (event.getAction() == MotionEvent.ACTION_UP) {

performClick();

}

if (common.isWindowInFocus && common.isImageLoaded) {

if (common.isPuzzleSolved) {

common.toggleUIOverlay(context);

return false;

} else {

if (puzzle != null) {

return puzzle.onTouchEvent(event);

}

}

}

return super.onTouchEvent(event);

}

}

createNewSizedPuzzle() We call this method when we have resized to a new puzzle size, we need to use the integer variable sent

to the method to determine how many pieces we will build the puzzle into. We also set the layout to

hidden so the user is ready to interact with a new puzzle, unobstructed.

public void createNewSizedPuzzle(int sides) {

if (common.isLogging)

Log.d(TAG, "createNewSizedPuzzle PuzzleSurface");

common.isImageLoaded = false;

puzzle = new AdjustablePuzzle(puzzleSurface);

puzzle.initPieces(sides);

puzzle.getNewImageLoadedScaledDivided();

common.hideButtons(context);

}

createPuzzle() Here we create a new puzzle. The current settings for the puzzle size are already set in our common

variables class.

public void createPuzzle() {

if (common.isLogging)

Log.d(TAG, "createPuzzle PuzzleSurface");

common.isImageLoaded = false;

puzzle = new AdjustablePuzzle(puzzleSurface);

puzzle.initPieces(3);

puzzle.getNewImageLoadedScaledDivided();

common.hideButtons(context);

}

resumePuzzle() Resuming the puzzle from the saved state means that we will need to set the number of sides ourselves

and then create the puzzle. The sides variables should have been set from shared preferences.

public void resumePuzzle() {

if (common.isLogging)

Log.d(TAG, "resumePuzzle PuzzleSurface");

common.isImageLoaded = false;

puzzle = new AdjustablePuzzle(puzzleSurface);

int sides = (int) common.dimensions;

puzzle.initPieces(sides);

puzzle.getPreviousImageLoadedScaledDivided();

common.hideButtons(context);

}

musicActivity() Launching the music activity will take the user to the link declared in the string file. I also update the

number of blog links traversed and then start the activity. Before doing this I hide the UI as well so other

button interactions aren’t accidentally pressed while the intent loads.

public void musicActivity() {

common.hideButtons(context);

Intent intent1 = new Intent(Intent.ACTION_VIEW);

intent1.setData(Uri.parse(context

.getString(R.string.music_link)));

common.blogLinksTraversed++;

context.startActivity(intent1);

}

ma009Activity() This is the same launcher activity but it uses the link declared in the strings file for my blog. I try not to

dive too deep into shameless self-promotion, but what the hey!

public void ma009Activity() {

common.hideButtons(context);

Intent intent1 = new Intent(Intent.ACTION_VIEW);

intent1.setData(Uri.parse(context

.getString(R.string.ma009_link)));

common.blogLinksTraversed++;

context.startActivity(intent1);

}

instagramActivity() The method starts by searching for Instagram™ to be loaded into the device and if the user doesn’t have

Instagram installed in their phone we will load the link to the artist’s blog instead through the browser.

public void instagramActivity() {

common.hideButtons(context);

String scheme = context.getString(R.string.instagram_user);

String path = context.getString(R.string.instagram_path);

String nomPackageInfo = context.getString(R.string.instagram_package);

Intent intent;

try {

context.getPackageManager().getPackageInfo(nomPackageInfo, 0);

intent = new Intent(Intent.ACTION_VIEW, Uri.parse(scheme));

} catch (PackageManager.NameNotFoundException e) {

intent = new Intent(Intent.ACTION_VIEW, Uri.parse(path));

e.printStackTrace();

}

common.blogLinksTraversed++;

context.startActivity(intent);

}

blogActivity() The blog activity launcher loads up a quick dialog that confirms for the user that they will be going to

DeviantArt and that the art may be more mature than what is seen in this application. If the user

confirms this they will be taken to DeviantArt. I set the positive text to confirm and the negative buttons

text is set to cancel. There is also a neutral button option you can use, any more will require a custom

layout. You can add any code into each button, but the naming convention is there for you to use to

help structure your code.

public void blogActivity() {

Activity act = (Activity) context;

AlertDialog.Builder builder = new AlertDialog.Builder(act);

builder.setTitle(common.res.getString(R.string.deviant_title));

builder.setMessage(context.getString(R.string.artist) +

common.res.getString(R.string.deviant_message))

.setPositiveButton(common.res.getString(R.string.continue_desc), new

DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int id) {

common.hideButtons(context);

Intent intent2 = new Intent(Intent.ACTION_VIEW);

intent2.setData(Uri.parse(context.getString(R.string.deviant_art_link)));

common.blogLinksTraversed++;

context.startActivity(intent2);

}

})

.setNegativeButton(common.res.getString(R.string.cancel), new

DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int id) {

//close dialog

}

});

builder.show();

}

cleanup() Clean up will be used to make sure all bitmaps are recycled and the memory associated with them is

freed up to prevent memory leaks.

public void cleanUp() {

if (puzzle != null)

puzzle.recycleAll();

}

getSlotString() In this method we will get each slot string and place its value in a comma separated string, this will

determine the size and placement of pieces in our puzzle on stop and resuming of the application if we

need to recreate the puzzle from the saved state.

public String getSlotString() {

String s = "";

if (common.puzzleSlots != null)

for (int i = 0; i < common.puzzleSlots.length; i++) {

if (common.puzzleSlots[i] != null) {

if (i == 0) {

s = "" + common.puzzleSlots[i].puzzlePiece.pieceNum;

} else {

s = s + "," + common.puzzleSlots[i].puzzlePiece.pieceNum;

}

}

}

return s;

}

newPuzzle() When this method is called the user has pressed the menu button for a new puzzle. We will show the

user a dialog entering a new puzzle size. You can change the default size if you wish. When getting the

input we will try to parse the input as a number that is valid and if this isn’t the case we will show a quick

Toast message to the user that they need to enter a new value.

public void newPuzzle() {

AlertDialog.Builder builder;

builder = new AlertDialog.Builder(context);

builder.setTitle("Create New Puzzle");

builder.setMessage("Enter number of sides 2 - 7");

final EditText inputH = new EditText(context);

inputH.setInputType(InputType.TYPE_CLASS_NUMBER);

inputH.setText(defaultPuzzleSize);

builder.setView(inputH);

builder.setPositiveButton("Create",

new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int whichButton) {

try {

String s = inputH.getText().toString();

s.replaceAll("[^0-9]", "");

int sides = Integer.parseInt(s);

if (sides > 7 || sides < 2) {

common.showToast(context, "2 to 7 dimension limit");

} else {

createNewSizedPuzzle(sides);

}

} catch (NumberFormatException nfe) {

common.showToast(context, "Unable to parse number

entered.");

}

}

});

builder.setNegativeButton("Cancel",

new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int whichButton) {

// Canceled.

}

});

dialog = builder.show();

}

toggleMusic() Setting this from the menu options will turn off or on our MediaPlayer, store the setting and then alert

the user they have changed the setting.

public void toggleMusic() {

if (common.playMusic) {

common.playMusic = false;

myMediaPlayer.pause();

common.showToast(context, "Music Off");

} else {

common.playMusic = true;

myMediaPlayer.resume();

common.showToast(context, "Music On");

}

}

toggleBorder() The puzzle has a border around each puzzle piece, this can be removed with this menu setting option.

public void toggleBorder() {

if (common.drawBorders) {

common.drawBorders = false;

common.showToast(context, "Borders Off");

} else {

common.drawBorders = true;

common.showToast(context, "Borders On");

}

}

toggleWinSound() There is a chime sound on solving a puzzle and creating a new puzzle this can be toggled on and off for

the user.

public void toggleWinSound() {

if (common.playChimeSound) {

common.playChimeSound = false;

common.showToast(context, "Win Effect Off");

} else {

common.playChimeSound = true;

common.showToast(context, "Win Effect On");

}

}

toggleSetSound() The tap sound you hear when placing a piece int a new slot can also be toggled on and off.

public void toggleSetSound() {

if (common.playTapSound) {

common.playTapSound = false;

common.showToast(context, "Set Effect Off");

} else {

common.playTapSound = true;

common.showToast(context, "Set Effect On");

}

}

saveMusic() The user can save the music track provided by using the save music menu option. In this class we call the

SaveMuscService class to do the work in the background. We can catch the result by sending the request

code as an extra.

public void saveMusic() {

PendingIntent pendingResult = ((Activity) context).createPendingResult(

SAVE_MUSIC_REQUEST_CODE, new Intent(), 0);

Intent intent = new Intent(context,

puzzle.template.save.SaveMusicService.class);

intent.putExtra(SaveMusicService.PENDING_RESULT_ACTION, pendingResult);

context.startService(intent);

}

onPause() In this method we will send the work to our puzzle update and draw class to pause its state.

public void onPause() {

if (common.isLogging)

Log.d(TAG, "onPause PuzzleSurface");

if (puzzleUpdateAndDraw != null) {

puzzleUpdateAndDraw.pause();

}

}

PuzzleUpdateAndDraw This class is an inner class of PuzzleSurface and does some work for use regarding the canvas that is

provided by our SurfaceHolder.

PuzzleUpdateAndDraw() We send to our constructor the context and surface holder for later reference.

public PuzzleUpdateAndDraw(SurfaceHolder surfaceHolder,

Context context) {

mSurfaceHolder = surfaceHolder;

}

updateAndDraw() We can update the canvas by calling updatePhysics and doDraw after we have locked the canvas. After

we run the methods we can unlock the canvas so it can be redrawn with the new state.

public void updateAndDraw() {

if (common.isLogging)

Log.d(TAG, "updateAndDraw PuzzleSurface");

Canvas c = null;

try {

c = mSurfaceHolder.lockCanvas(null);

if (c != null) {

synchronized (mSurfaceHolder) {

updatePhysics();

doDraw(c);

}

}

} finally {

if (c != null) {

mSurfaceHolder.unlockCanvasAndPost(c);

}

}

}

getSurfaceHolder() I use the surface holder as our synchronizing variable when performing updates during onTouch. This

will prevent other methods from manipulating our surface while it is already in use.

public SurfaceHolder getSurfaceHolder() {

return mSurfaceHolder;

}

updatePhysics() We use our CommonVariables class to update the UI if the puzzle is solved. This involves showing or

hiding the buttons if the puzzle is solved.

private void updatePhysics() {

if (common.isLogging)

Log.d(TAG, "updatePhysics PuzzleSurface");

if (common.isPuzzleSolved) {

common.showButtons(context);

}

}

doDraw() We will perform the draw update by setting the canvas to a black background and then draw every

piece as it is or draw every piece as well as a moving piece. If we are loading a new image and there is an

error we can also draw a solid color to help keep the UI from appearing frozen. I have rarely seen these

colors appear as the loading and dividing of the bitmap usually happens quickly and efficiently enough

to not give the user a loading screen or error state.

private void doDraw(Canvas canvas) {

if (common.isLogging)

Log.d(TAG, "doDraw PuzzleSurface");

if (canvas != null) {

canvas.drawColor(Color.BLACK);

if (common.isImageLoaded) {

if (common.movingPiece) {

drawImageWithMovingPiece(canvas);

} else {

drawImage(canvas);

}

} else {

// the imageID is still loading or in error

if (common.isImageError) {

canvas.drawColor(Color.RED);

} else {

canvas.drawColor(Color.BLUE);

}

}

}

}

drawImage() When we are ready to draw our image to the screen, we can iterate over every puzzle bitmap piece and

draw this piece in its place relative to the slot it belongs to. We have a second draw that draws the

border to each puzzle piece as well.

private void drawImage(Canvas canvas) {

if (common.isLogging)

Log.d(TAG, "drawImage PuzzleSurface");

for (int i = 0; i < common.numberOfPieces; i++) {

if (!common.puzzleSlots[i].puzzlePiece.bitmap

.isRecycled()) {

// draw pieces

canvas.drawBitmap(

common.puzzleSlots[i].puzzlePiece.bitmap,

common.puzzleSlots[i].puzzlePiece.px,

common.puzzleSlots[i].puzzlePiece.py, null);

// draw borders

if (!common.isPuzzleSolved && common.drawBorders) {

canvas.drawRect(

common.puzzleSlots[i].sx,

common.puzzleSlots[i].sy,

common.puzzleSlots[i].sx

+ common.puzzleSlots[i].puzzlePiece.bitmap

.getWidth(),

common.puzzleSlots[i].sy

+ common.puzzleSlots[i].puzzlePiece.bitmap

.getHeight(),

borderPaintA);

}

}

}

}

drawImageWithMovingPiece() In this method we need to account for the puzzle to have a moving piece, this means that one piece will

not be drawn in its place relative to the slot it belongs to, but relative to the x and y touch coordinates

received. I also draw a shadowed version of the moving piece in the original slot location to give the hint

that this piece is not there and in the process of being moved to a new slot. I store the moving piece

coordinates in the common variables.

private void drawImageWithMovingPiece(Canvas canvas) {

for (int i = 0; i < common.numberOfPieces; i++) {

// draw pieces

if (!common.puzzleSlots[i].puzzlePiece.bitmap

.isRecycled()

&& common.currSlotOnTouchDown != i)

canvas.drawBitmap(

common.puzzleSlots[i].puzzlePiece.bitmap,

common.puzzleSlots[i].puzzlePiece.px,

common.puzzleSlots[i].puzzlePiece.py, null);

// draw border to pieces

if (!common.isPuzzleSolved && common.drawBorders)

canvas.drawRect(

common.puzzleSlots[i].sx,

common.puzzleSlots[i].sy,

common.puzzleSlots[i].sx

+ common.puzzleSlots[i].puzzlePiece.bitmap

.getWidth(),

common.puzzleSlots[i].sy

+ common.puzzleSlots[i].puzzlePiece.bitmap

.getHeight(),

borderPaintA);

}

// draw moving piece and its shadow

if (!common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.bitmap

.isRecycled()) {

// draw moving imageID in original location

canvas.drawBitmap(

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.bitmap,

common.puzzleSlots[common.currSlotOnTouchDown].sx,

common.puzzleSlots[common.currSlotOnTouchDown].sy,

transPaint);

// draw border around original piece location

canvas.drawRect(

common.puzzleSlots[common.currSlotOnTouchDown].sx,

common.puzzleSlots[common.currSlotOnTouchDown].sy,

common.puzzleSlots[common.currSlotOnTouchDown].sx

+

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.bitmap

.getWidth(),

common.puzzleSlots[common.currSlotOnTouchDown].sy

+

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.bitmap

.getHeight(), borderPaintB);

// draw moving piece

canvas.drawBitmap(

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.bitmap,

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.px,

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.py,

fullPaint);

// draw border around moving piece

if (common.drawBorders)

canvas.drawRect(

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.px

+ (STROKE_VALUE / 2),

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.py

+ (STROKE_VALUE / 2),

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.px

+

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.bitmap

.getWidth()

- (STROKE_VALUE / 2),

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.py

+

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.bitmap

.getHeight()

- (STROKE_VALUE / 2),

borderPaintA);

}

}

surfaceChanged() The surface is changed when the screen size is set to the surface, at this time we can save the variables

relative to the new puzzle size we need to draw based on the new width and height.

public void surfaceChanged(int width, int height) {

// synchronized to make sure these all change atomically

synchronized (mSurfaceHolder) {

common.screenW = width;

common.screenH = height;

}

}

pause() When we are ready to pause the surface we need to also pause the puzzle. The puzzle has a timer for

keeping track of the time to solve a puzzle, we need to call pause here so we have a time stamp of the

paused time. We keep the overall time to solve a puzzle intact by adding the missing time to the timer

on resume of the puzzle, if a puzzle sits overnight in the saved state it take this into account. I thought it

might seem funny since the application is not running and the overall time might be higher than

expected but I like the idea that a person can’t close the app after taking a screen shot and then return

and quickly solve after taking a screenshot or some other device to fool the timer.

public void pause() {

synchronized (mSurfaceHolder) {

if (puzzle != null)

puzzle.pause();

}

}

AdjustablePuzzle.java The adjustable puzzle class scales the puzzle to different sizes as needed. We will need the puzzle

surface and the common variables class to build our puzzle.

getPreviousImageLoadedScaledDivided() This method will handle all of the logic to get the previous image from our saved settings. We can get

the image after setting the state of our puzzle to not split correctly and that the current puzzle is not

solved. This will prevent interaction with a puzzle that is not ready and will confirm that the current

image has been split correctly. All of the real work that will be done is done in a Thread off of the main

UI thread. The method calls for decoding and scaling are using code that will create a bitmap that is

appropriate for the device the application is currently installed on. I use code from the Android samples

for BitmapFun found here…

https://developer.android.com/training/displaying-bitmaps/index.html

public void getPreviousImageLoadedScaledDivided() {

if (common.isLogging)

Log.d(TAG, "getPreviousImageLoadedScaledDivided AdjustablePuzzleImpl");

common.isPuzzleSplitCorrectly = false;

common.isPuzzleSolved = false;

Thread thread = new Thread() {

@Override

public void run() {

while (!common.isPuzzleSplitCorrectly) {

// get new index value and then remove index

common.index = common.currentPuzzleImagePosition;

common.image = common.decodeSampledBitmapFromResource(

common.res,

Data.PICS[common.currentPuzzleImagePosition],

common.screenW, common.screenH);

common.image = Bitmap.createScaledBitmap(common.image,

common.screenW, common.screenH, true);

common.isPuzzleSplitCorrectly = divideBitmapFromPreviousPuzzle();

if (common.isPuzzleSplitCorrectly) {

common.isImageError = false;

common.mySoundPool.playChimeSound();

common.isImageLoaded = true;

checkToBeSolved();

updateAndDraw();

} else {

common.isImageError = true;

}

}

}

};

thread.start();

}

getNewImageLoadedScaledDivided() Instead of getting a previous image we will scale a new image to set as the puzzle. A main difference in

this and the previous method is how we will keep track of the currently used images. They are stored in

an array called imagesShown, in our common variables class. This will keep a list of numbers that

correlate to images that have not been shown. If the list is empty we can populate it with every image in

the application. As we use an image we remove the image number. From here we continue to decode,

resize and then divide and disperse the image’s subsections as pieces. We will go into more detail as the

calling methods are explored in the guide.

public void getNewImageLoadedScaledDivided() {

if (common.isLogging)

Log.d(TAG, "getNewImageLoadedScaledDivided AdjustablePuzzleImpl");

common.isPuzzleSplitCorrectly = false;

common.isPuzzleSolved = false;

Thread thread = new Thread() {

@Override

public void run() {

while (!common.isPuzzleSplitCorrectly) {

// fill with all valid numbers

if (common.imagesShown.isEmpty()) {

for (int i = 0; i < Data.PICS.length; i++) {

common.imagesShown.add(i);

}

}

// get new index value from remaining images

common.index = common.rand.nextInt(common.imagesShown

.size());

//edit to change to a direct image

//common.index = 141;

// get the value at that index for new imageID

common.currentPuzzleImagePosition = common.imagesShown

.get(common.index);

// remove from list to prevent duplicates

common.imagesShown.remove(common.index);

// start decoding and scaling

common.image = common.decodeSampledBitmapFromResource(

common.res,

Data.PICS[common.currentPuzzleImagePosition],

common.screenW, common.screenH);

common.image = Bitmap.createScaledBitmap(common.image,

common.screenW, common.screenH, true);

common.isPuzzleSplitCorrectly = divideBitmap();

if (common.isPuzzleSplitCorrectly) {

resetTimer();

common.isImageError = false;

common.mySoundPool.playChimeSound();

common.isImageLoaded = true;

checkToBeSolved();

updateAndDraw();

} else {

common.isImageError = true;

}

}

}

};

thread.start();

}

divideBitmap() This method is called when we have an image and we are ready to split the image apart and start getting

the x and y coordinates to each piece. Once we have these set we can begin to switch the pieces with

other pieces for a random set of indexes and this will create a random puzzle. When we call init divide

bitmap we are setting up all of the pieces in slots. Think of a tic tac toe board as the slots in a 3 x 3

puzzle, the whole screen is divided into equal sections and each section or slot will hold a bitmap. We

will keep the slots in their original locations while moving the pieces to different slots. Each slot and

piece is numbered so we will know the puzzle is solved when every piece number is back in its equal slot

number. We track the x and y of each top left corner so we can tell the pixel area of each section.

public boolean divideBitmap() {

common.initDivideBitmap(pieces);

assignXandYtoBorderPointIndex();

randomizeSlotOrderForPuzzle();

return common.assignSlotOrder();

}

assignXandYtoBorderPointIndex() From this method we will assign the x and y coordinates to the slots and begin to assign subsections of

the image to slots. We are assigning not only the subsections of the bitmap to the pieces but setting the

x and y values of the points to the slots. We use this point so on start of the puzzle we have the x and y

coordinates of the slots and pieces matching.

public void assignXandYtoBorderPointIndex() {

common.evenlySplit = false;

common.piecesComplete = 0;

while (!common.evenlySplit) {

// get screen width and height to start splitting

int width = 0;

if (common.image != null) {

width = common.image.getWidth();

}

int height = 0;

if (common.image != null) {

height = common.image.getHeight();

}

int pieceW = width / xParts;

int pieceH = height / yParts;

common.xs = new int[xParts];

for (int i = 0; i < xParts; i++) {

common.xs[i] = pieceW * i;

}

common.ys = new int[yParts];

for (int i = 0; i < yParts; i++) {

common.ys[i] = pieceH * i;

}

int acc = 0;

for (int i = 0; i < common.ys.length; i++) {

int tempy = common.ys[i];

for (int j = 0; j < common.xs.length; j++) {

int tempx = common.xs[j];

setBorderPoint(acc, tempx, tempy);

setBitmapToPiece(acc, tempx, tempy, pieceW, pieceH);

setPointsToSlotAndPiece(acc, tempx, tempy, pieceW, pieceH);

acc++;

}

}

common.evenlySplit = true;

}

}

setBorderPoint() In this method we are setting the x and y coordinates of each top and left corner of a section, when

building the rest we have the size of each subsection to tell the exact area of each section.

private void setBorderPoint(int i, int x, int y) {

// add point to array for x and y of each division intersection of

// pieces

Point newPoint = new Point(x, y);

common.points[i] = newPoint;

}

divideBitmapFromPreviousPuzzle() We won’t need a value from a previous puzzle, if we are in this method we are using the values from our

saved state in dividing and resetting our puzzle.

public boolean divideBitmapFromPreviousPuzzle() {

common.initPrevDivideBitmap(pieces);

assignXandYtoBorderPointIndex();

return common.assignSlotOrder();

}

setPointsToSlotAndPiece() From this method we will use the x and y values of each border top left corner subsection to establish

the placement of each bitmap subsection. We have the subsection height and width to tell the right and

bottom corners on the pixel grid.

private void setPointsToSlotAndPiece(int i, int x, int y, int bitmapW,

int bitmapH) {

common.puzzlePieces[i].px = x;

common.puzzlePieces[i].px2 = x + bitmapW;

common.puzzlePieces[i].py = y;

common.puzzlePieces[i].py2 = y + bitmapH;

common.puzzleSlots[i].sx = x;

common.puzzleSlots[i].sx2 = x + bitmapW;

common.puzzleSlots[i].sy = y;

common.puzzleSlots[i].sy2 = y + bitmapH;

common.puzzleSlots[i].puzzlePiece = common.puzzlePieces[i];

common.puzzleSlots[i].slotNum = common.puzzleSlots[i].puzzlePiece.pieceNum =

i;

common.piecesComplete++;

}

setBitmapToPiece() We have to recycle the section’s bitmap that is already in place if it does exist. After that is done we can

set the new piece by taking the current image that is the full image and creating a subsection from it,

this is why we have four integer parameters after the image parameter in the Bitmap classes

createBitmap method.

private void setBitmapToPiece(int i, int x, int y, int bitmapW, int bitmapH) {

if (common.puzzlePieces[i].bitmap != null) {

common.puzzlePieces[i].bitmap.recycle();

}

common.puzzlePieces[i].bitmap = null;

common.puzzlePieces[i].bitmap = Bitmap.createBitmap(common.image, x, y,

bitmapW, bitmapH);

}

updateAndDraw() We need to update and draw the puzzle but the work to be done will be handled in the surface’s puzzle

update and draw class.

public void updateAndDraw() {

puzzleSurface.puzzleUpdateAndDraw.updateAndDraw();

}

OnTouchEvent() We can handle the touch events here as they are received by the device and passed through our classes.

In this method we need to handle touch down, move, and up events. This can be broken down as the

user first touching a piece, while moving this piece we will need to update our x and y values for the

piece that is being moved so we draw it over the user’s touch and then we need to capture when they

release the touch and perform a piece switch at that point.

There are invalid x and y coordinates that can break our puzzle. For instance the user can touch over the

tool bar or even off screen in a negative pixel area, this will still register in our on touch method but we

don’t want to handle it. Once we get valid values we can store them in the common variables class as

well as storing the piece we are moving by figuring where in our subsection grid the touch occurred.

Moving a piece we can also run into trouble as the user may drag the piece off screen, in this case we

will return the piece to its original location. I also make a call to update and draw as the user performs

move actions.

On touch up event we also need to account for invalid locations that aren’t on our canvas. If this is the

case we can return the piece to its original slot. We can make a call to play our piece set sound here as

well as check that the puzzle is solved or not here.

To help reduce the amount of code here, I don’t perform any work here that can be handled by setting a

flag such as isPuzzlesSolved and then handling this in the update and draw class. Keeping the onTouch

methods as light as possible will keep your applications from skipping frames, eventually risking an ANR.

public boolean onTouchEvent(MotionEvent event) {

// find the piece that was pressed down onto

if (event.getAction() == MotionEvent.ACTION_DOWN) {

int downX = (int) event.getX();

int downY = (int) event.getY();

if (downX > common.screenW || downX < 0)

return false;

if (downY > common.screenH || downY < 0)

return false;

common.movingPiece = false;

// get x index

int xIndex = 0;

for (int i = 0; i < common.xs.length; i++) {

if (downX >= common.xs[i]) {

xIndex = i;

}

}

// get y index

int yIndex = 0;

for (int i = 0; i < common.ys.length; i++) {

if (downY >= common.ys[i]) {

yIndex = i;

}

}

//find the piece based on x and y matrix

common.currSlotOnTouchDown = xIndex + (yParts * yIndex);

} else if (event.getAction() == MotionEvent.ACTION_MOVE) {

// the moving piece has its own coordinates

int moveX = (int) event.getX();

int moveY = (int) event.getY();

boolean invalidMovePosition = false;

if (moveX > common.screenW || moveX < 0)

invalidMovePosition = true;

if (moveY > common.screenH || moveY < 0)

invalidMovePosition = true;

if (invalidMovePosition) {

common.movingPiece = false;

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.px =

common.puzzleSlots[common.currSlotOnTouchDown].sx;

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.py =

common.puzzleSlots[common.currSlotOnTouchDown].sy;

updateAndDraw();

return false;

}

// get moving piece and center it on user touch point

common.movingPiece = true;

if (common.currSlotOnTouchDown >= 0

&& common.currSlotOnTouchDown < pieces) {

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.px = moveX

-

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.bitmap

.getWidth() / 2;

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.py = moveY

-

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.bitmap

.getHeight() / 2;

}

updateAndDraw();

} else if (event.getAction() == MotionEvent.ACTION_UP) {

//the up action means it may be time to switch pieces store the new up

coords

int upX = (int) event.getX();

int upY = (int) event.getY();

common.movingPiece = false;

boolean invalidSetPosition = false;

if (upX > common.screenW || upX < 0)

invalidSetPosition = true;

if (upY > common.screenH || upY < 0)

invalidSetPosition = true;

if (invalidSetPosition) {

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.px =

common.puzzleSlots[common.currSlotOnTouchDown].sx;

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.py =

common.puzzleSlots[common.currSlotOnTouchDown].sy;

common.playSetSound();

updateAndDraw();

return false;

} else {

// get x index

int xIndex = 0;

for (int i = 0; i < common.xs.length; i++) {

if (upX >= common.xs[i]) {

xIndex = i;

}

}

// get y index

int yIndex = 0;

for (int i = 0; i < common.ys.length; i++) {

if (upY >= common.ys[i]) {

yIndex = i;

}

}

common.currSlotOnTouchUp = xIndex + (yParts * yIndex);

// check for new location to not be the original before setting

if (common.currSlotOnTouchDown != common.currSlotOnTouchUp) {

common.sendPieceToNewSlot(common.currSlotOnTouchDown,

common.currSlotOnTouchUp);

} else {

// simply return the moving piece to its original x and y

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.px =

common.puzzleSlots[common.currSlotOnTouchDown].sx;

common.puzzleSlots[common.currSlotOnTouchDown].puzzlePiece.py =

common.puzzleSlots[common.currSlotOnTouchDown].sy;

}

common.playSetSound();

// check for all images to by in place

common.inPlace = 0;

for (int i = 0; i < common.numberOfPieces; i++) {

if (common.puzzleSlots[i].slotNum ==

common.puzzleSlots[i].puzzlePiece.pieceNum) {

common.inPlace++;

}

}

// if all in place set as isPuzzleSolved

if (common.inPlace == common.numberOfPieces) {

addTimeToTimer();

common.isPuzzleSolved = true;

//increment stats

common.puzzlesSolved++;

incrementPuzzleSolveByPuzzleSize();

compareRecordTime();

//notify stats fragment if alive

MainActivity main = (MainActivity) puzzleSurface.context;

main.updatePuzzleStats();

updateAndDraw();

return false;

} else {

updateAndDraw();

}

}

}

return true;

}

incrementPuzzleSolvedByPuzzleSize() We are keeping track of the number of puzzles solved per size of the puzzle, this is done by switching on

the pieces variable. The pieces can tell us the size of or grid as well, if we have 4 pieces we know we are I

the 2 x 2 grid.

private void incrementPuzzleSolveByPuzzleSize() {

switch (pieces) {

case 4:

common.fourPiecePuzzleSolvedCount++;

break;

case 9:

common.ninePiecePuzzleSolvedCount++;

break;

case 16:

common.sixteenPiecePuzzleSolvedCount++;

break;

case 25:

common.twentyfivePiecePuzzleSolvedCount++;

break;

case 36:

common.thirtysixPiecePuzzleSolvedCount++;

break;

case 49:

common.fourtyninePiecePuzzleSolvedCount++;

break;

}

}

recycleAll() This method is a clean-up method that checks for all bitmaps in the slots to exist, recycle them to free

memory and finally sets them to null. It is important to do so since bitmaps use up a good deal of

memory and will cause leaks if left un-recycled.

public void recycleAll() {

if (common.image != null)

common.image.recycle();

for (int i = 0; i < common.puzzlePieces.length; i++)

if (common.puzzlePieces != null)

if (common.puzzlePieces[i] != null)

if (common.puzzlePieces[i].bitmap != null)

common.puzzlePieces[i].bitmap.recycle();

}

addTimeToTimer() This will add time to our puzzle timer for keeping track of how long the puzzle has been left unsolved.

The timer will keep running even while the app is closed. I figured this might not be liked, but it is still

time that the users didn’t solve this puzzle!

public void addTimeToTimer() {

if (!common.isPuzzleSolved) {

common.stopPuzzle = new Date();

common.currPuzzleTime += common.stopPuzzle.getTime()

- common.startPuzzle.getTime();

}

}

compareRecordTime() After solving a puzzle we can check if the time it took to solve this puzzle is faster than the current

record time. If it is we will replace the value and this will update our stats page as well as the saved stat

file on stop of the application.

public void compareRecordTime() {

switch (pieces) {

case 4:

if (common.fourRecordSolveTime > common.currPuzzleTime ||

common.fourRecordSolveTime == 0) {

common.fourRecordSolveTime = common.currPuzzleTime;

}

break;

case 9:

if (common.nineRecordSolveTime > common.currPuzzleTime ||

common.nineRecordSolveTime == 0) {

common.nineRecordSolveTime = common.currPuzzleTime;

}

break;

case 16:

if (common.sixteenRecordSolveTime > common.currPuzzleTime ||

common.sixteenRecordSolveTime == 0) {

common.sixteenRecordSolveTime = common.currPuzzleTime;

}

break;

case 25:

if (common.twentyFiveRecordSolveTime > common.currPuzzleTime ||

common.twentyFiveRecordSolveTime == 0) {

common.twentyFiveRecordSolveTime = common.currPuzzleTime;

}

break;

case 36:

if (common.thirtySixRecordsSolveTime > common.currPuzzleTime ||

common.thirtySixRecordsSolveTime == 0) {

common.thirtySixRecordsSolveTime = common.currPuzzleTime;

}

break;

case 49:

if (common.fourtyNineRecordsSolveTime > common.currPuzzleTime ||

common.fourtyNineRecordsSolveTime == 0) {

common.fourtyNineRecordsSolveTime = common.currPuzzleTime;

}

break;

}

}

resetTimer() On start of a new puzzle we will reset the Timer. Creating a new Date() object will use the current time

by default as its value.

public void resetTimer() {

common.currPuzzleTime = 0;

common.startPuzzle = new Date();

}

getPuzzleTime() Since our timer uses milliseconds we will have to divide the stored amount by 1000 in order to have

seconds for our print out.

public double getSolveTime() {

return common.currPuzzleTime / 1000.0;

}

onPause() We will add the current time to our running timer when the app is paused.

public void pause() {

addTimeToTimer();

}

initPieces() We can tell the number of pieces by squaring the sides and use the xparts and yparts variables for

dividing the full screen into a grid.

public void initPieces(int sides) {

xParts = sides;

yParts = sides;

pieces = sides * sides;

}

checkToBeSolved() We can easily check if the puzzle is solved if we have each piece’s number in the same numbered slot.

We search every slot for its matching piece, and increment our counter if this true. We know if the

puzzles are solved when the number of in place pieces equal to the number of pieces in our puzzle.

public void checkToBeSolved() {

common.inPlace = 0;

for (int i = 0; i < common.numberOfPieces; i++) {

if (common.puzzleSlots[i].slotNum ==

common.puzzleSlots[i].puzzlePiece.pieceNum) {

common.inPlace++;

}

}

if (common.inPlace == common.numberOfPieces) {

common.isPuzzleSolved = true;

}

}

CommonVariables Our common variables class is a Singleton class. This can be read up more in detail at Java official

documentation below, it’s a handy design principle to make data available throughout an application.

http://www.oracle.com/technetwork/articles/java/singleton-1577166.html

If I give an abbreviated explanation, a singleton class creates itself and then owned by no one but can be

accessed by everyone and will give everyone the same data values. This is great for having a place to

store variables that will be needed across the application in different places. As the puzzle is built and

solved we will need the same values in our stats fragment so we need the values to be the same when

we access them. Instead of saving them to a file and getting them we can hold them in our singleton and

access them much faster. The class is mostly variables but since some of the work may be needed I

modify this class to do some work in here as well as needed.

getInstance() The class is never called directly with a new statement. Instead you have the class itself make a version

and hand it to the class that needs it or return the instance of it created already. We can help prevent

outside classes from creating a new instance by making the local instance private and have a private

constructor for the class.

public static CommonVariables getInstance() {

if (instance == null)

synchronized (CommonVariables.class) {

if (instance == null)

instance = new CommonVariables();

}

return instance;

}

private CommonVariables() {

}

CommonVariables() Notice the constructor is private, with this only the class object itself can build an instance of itself.

private CommonVariables() {

}

setSlots() When creating the array to hold the slots we are given a string of comma separated slot values, we can

tell the size of the array after we parse out the commas and place each index into a string array. This size

will be the same

While we are turning the string values into integers we should make sure that each number is parsed

correctly and if it isn’t we can return false.

public boolean setSlots(String string) {

String[] stringSlots;

stringSlots = string.split(",");

slotOrder = new int[stringSlots.length];

for (int i = 0; i < stringSlots.length; i++) {

try {

slotOrder[i] = Integer.parseInt(stringSlots[i]);

} catch (NumberFormatException nfe) {

return false;

}

}

return true;

}

assignSlotOrder() This method will assign a new array of PuzzleSlot’s with a new set of pieces and array values. Each new

slot will be given new x and y coordinates as well as new numbering for its slot number. The slot has a

new piece and also has new puzzle piece x and y coordinates.

The method will return false if the puzzle was not able to make the puzzle randomly. Meaning every

piece number has to be away from its slot number.

public boolean assignSlotOrder() {

PuzzleSlot[] tempSlots = new PuzzleSlot[numberOfPieces];

for (int i = 0; i < numberOfPieces; i++) {

tempSlots[i] = new PuzzleSlot();

tempSlots[i].sx = puzzleSlots[i].sx;

tempSlots[i].sy = puzzleSlots[i].sy;

tempSlots[i].sx2 = puzzleSlots[i].sx2;

tempSlots[i].sy2 = puzzleSlots[i].sy2;

tempSlots[i].slotNum = puzzleSlots[i].slotNum;

//assign random piece to the current slot

tempSlots[i].puzzlePiece = puzzleSlots[slotOrder[i]].puzzlePiece;

//assign the new piece's coordinates from the slot it now is in

tempSlots[i].puzzlePiece.px = tempSlots[i].sx;

tempSlots[i].puzzlePiece.py = tempSlots[i].sy;

}

puzzleSlots = tempSlots;

return piecesComplete == numberOfPieces;

}

calculateInSampleSize() Android provides this method which will return the smallest ratio for scaling. The code returns the lesser

of height and width ratio variables.

public int calculateInSampleSize(BitmapFactory.Options options,

int reqWidth, int reqHeight) {

// Raw height and width of image

final int height = options.outHeight;

final int width = options.outWidth;

int inSampleSize = 1;

if (height > reqHeight || width > reqWidth) {

final int halfHeight = height / 2;

final int halfWidth = width / 2;

// Calculate the largest inSampleSize value that is a power of 2 and keeps

both

// height and width larger than the requested height and width.

while ((halfHeight / inSampleSize) >= reqHeight

&& (halfWidth / inSampleSize) >= reqWidth) {

inSampleSize *= 2;

}

}

return inSampleSize;

}

You can use this method to help bitmaps fit your device. Review the sample project BitmapFun to see

more code samples using these methods.

decodeSampledBitmapFromResource() This is another Android given method for scaling our bitmaps to the full screen dimensions.

public Bitmap decodeSampledBitmapFromResource(Resources res, int resId,

int reqWidth, int reqHeight) {

// First decode with inJustDecodeBounds=true to check dimensions

final BitmapFactory.Options options = new BitmapFactory.Options();

options.inJustDecodeBounds = true;

BitmapFactory.decodeResource(res, resId, options);

// Calculate inSampleSize

options.inSampleSize = calculateInSampleSize(options, reqWidth,

reqHeight);

// Decode bitmap with inSampleSize set

options.inJustDecodeBounds = false;

return BitmapFactory.decodeResource(res, resId, options);

}

You can use this method to find the dimensions to return the bitmap as per the device the user is on.

Mobile devices have great processor’s and good storage but are poor in memory, so using helper

methods like the one’s given are essential to making a good running application across different devices.

sendPieceToNewSlot() Sending a piece of a puzzle to a new slot piece means we will have to perform a swap of one piece with

another. In the meantime of setting our piece to a new slot we will have to back up the slot’s previous

piece and then send it to the new slot’s piece.

public void sendPieceToNewSlot(int a, int z) {

PuzzlePiece temp;

temp = puzzleSlots[currSlotOnTouchDown].puzzlePiece;

puzzleSlots[a].puzzlePiece = puzzleSlots[z].puzzlePiece;

puzzleSlots[a].puzzlePiece.px = puzzleSlots[a].sx;

puzzleSlots[a].puzzlePiece.py = puzzleSlots[a].sy;

puzzleSlots[z].puzzlePiece = temp;

puzzleSlots[z].puzzlePiece.px = puzzleSlots[z].sx;

puzzleSlots[z].puzzlePiece.py = puzzleSlots[z].sy;

}

showToast() Showing a toast message is handled in common variables where the reference to the activities context is

stored and where we can handle keeping track of the current message being shown. If we are already

showing a message we don’t want to create a new one, we would rather overwrite the current message

and then re-show it for the user or cancel the current one out and then overwrite and re-show.

public void showToast(Context context, String message) {

// Create and show toast for save photo

Activity act = (Activity) context;

if (toast == null) {

toast = Toast.makeText(act, 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();

}

}

playSetSound() The method called to play the sound when a puzzle piece is set to a new slot. The work to perform

playing the sound will be done in the MySoundPool class.

public void playSetSound() {

mySoundPool.playSetSound();

}

initPrevDivideBitmap() Initializing the puzzle depends in if it is a new puzzle or not. When we are restoring a previous bitmap

we already have a slot order to re assemble the puzzle into. We will need to initialize the pieces and

slots into new objects as well as set the overall number of pieces that the puzzle will hold.

public void initPrevDivideBitmap(int pieces) {

numberOfPieces = pieces;

points = new Point[pieces];

puzzlePieces = new PuzzlePiece[pieces];

for (int i = 0; i < numberOfPieces; i++) {

puzzlePieces[i] = new PuzzlePiece();

}

puzzleSlots = new PuzzleSlot[pieces];

for (int i = 0; i < numberOfPieces; i++) {

puzzleSlots[i] = new PuzzleSlot();

}

}

initDivideBitmap() We need to do the same work of initializing a puzzle but this time since we are starting from a new

puzzle we can create our slot ordering by default to be in perfect order. We will randomize it before

displaying the puzzle. The puzzle array has N number of pieces but the puzzle piece count is N2. The

puzzle pieces are stored in the array in a straight line, moving horizontally and then wrapping as the row

ends. This means that in a 9 piece puzzle the second row and first column would be puzzle piece number

4 and index 3 for arrays.

public void initDivideBitmap(int pieces) {

numberOfPieces = pieces;

points = new Point[pieces];

// setup puzzle pieces with new pieces

puzzlePieces = new PuzzlePiece[pieces];

for (int i = 0; i < numberOfPieces; i++) {

puzzlePieces[i] = new PuzzlePiece();

}

// setup for new slots for the pieces

puzzleSlots = new PuzzleSlot[pieces];

for (int i = 0; i < numberOfPieces; i++) {

puzzleSlots[i] = new PuzzleSlot();

}

// default order for slots with perfect order

slotOrder = new int[pieces];

for (int i = 0; i < pieces; i++) {

slotOrder[i] = i;

}

}

hideButtons() The UI on puzzle solve shows link buttons and other widgets for user interaction, mostly devoted to

social media and linking users to more of the artist’s work but you can use these for any site or blog that

you like. If you are running a game thread that runs your engine, it is probably running off of the UI

thread. The main UI thread cycles through and can’t me made to wait for longer than five seconds

before an Android application will crash from ANR. Doing work off of the UI thread will help give your

application the appearance of running on one thread and give a seamless experience to the user.

public void hideButtons(Context context) {

if (isLogging)

Log.d(TAG, "hideButtons CommonVariables");

Activity act = (Activity) context;

act.runOnUiThread(new Runnable() {

@Override

public void run() {

if (mNextButton != null

&& mNextButton.getVisibility() == View.VISIBLE) {

mNextButton.setVisibility(View.INVISIBLE);

}

if (musicButton != null

&& musicButton.getVisibility() == View.VISIBLE) {

musicButton.setVisibility(View.INVISIBLE);

}

if (blogButton != null

&& blogButton.getVisibility() == View.VISIBLE) {

blogButton.setVisibility(View.INVISIBLE);

}

if (ma009Button != null

&& ma009Button.getVisibility() == View.VISIBLE) {

ma009Button.setVisibility(View.INVISIBLE);

}

if (instagramButton != null

&& instagramButton.getVisibility() == View.VISIBLE) {

instagramButton.setVisibility(View.INVISIBLE);

}

}

});

}

toggleUIOverlay() This method is similar to hide buttons but the difference is the feature that it enables hidden buttons to

be shown. This method is called from the users on touch interaction with a solved puzzle. The user will

see the image in the full screen view and then on another touch the UI will be shown again.

public void toggleUIOverlay(Context context) {

if (isLogging) {

Log.d(TAG, "toggleUIOverlay CommonVariables");

}

Activity act = (Activity) context;

act.runOnUiThread(new Runnable() {

@Override

public void run() {

if (mNextButton != null)

if (mNextButton.getVisibility() == View.VISIBLE)

mNextButton.setVisibility(View.INVISIBLE);

else {

mNextButton.setVisibility(View.VISIBLE);

mNextButton.bringToFront();

}

if (musicButton != null)

if (musicButton.getVisibility() == View.VISIBLE)

musicButton

.setVisibility(View.INVISIBLE);

else {

musicButton

.setVisibility(View.VISIBLE);

musicButton.bringToFront();

}

if (blogButton != null)

if (blogButton

.getVisibility() == View.VISIBLE)

blogButton

.setVisibility(View.INVISIBLE);

else {

blogButton

.setVisibility(View.VISIBLE);

blogButton.bringToFront();

}

if (ma009Button != null)

if (ma009Button

.getVisibility() == View.VISIBLE)

ma009Button

.setVisibility(View.INVISIBLE);

else {

ma009Button

.setVisibility(View.VISIBLE);

ma009Button.bringToFront();

}

if (instagramButton != null)

if (instagramButton

.getVisibility() == View.VISIBLE)

instagramButton

.setVisibility(View.INVISIBLE);

else {

instagramButton

.setVisibility(View.VISIBLE);

instagramButton.bringToFront();

}

}

});

}

showButtons() Showing the buttons is similar to hiding them, we do work again on the UI thread and make sure that in

the case that the OS is operating off of the UI thread that the UI will be able to be updated. On solve of

the puzzle we can show the buttons to the user.

public void showButtons(Context context) {

if (isLogging)

Log.d(TAG, "showButtons CommonVariables");

Activity act = (Activity) context;

act.runOnUiThread(new Runnable() {

@Override

public void run() {

if (mNextButton != null

&& mNextButton.getVisibility() == View.INVISIBLE) {

mNextButton.setVisibility(View.VISIBLE);

mNextButton.bringToFront();

}

if (musicButton != null

&& musicButton.getVisibility() == View.INVISIBLE) {

musicButton.setVisibility(View.VISIBLE);

musicButton.bringToFront();

}

if (blogButton != null

&& blogButton.getVisibility() == View.INVISIBLE) {

blogButton.setVisibility(View.VISIBLE);

blogButton.bringToFront();

}

if (ma009Button != null

&& ma009Button.getVisibility() == View.INVISIBLE) {

ma009Button.setVisibility(View.VISIBLE);

ma009Button.bringToFront();

}

if (instagramButton != null

&& instagramButton.getVisibility() == View.INVISIBLE) {

instagramButton.setVisibility(View.VISIBLE);

instagramButton.bringToFront();

}

}

});

}

Data.java Keeping track of the images, or at least the id’s for them, is done in a Data.java class that holds the

reference to the music track that will be played as well as a list of all the images ids in a static int array.

For ease of updating, images will be selected at random from the total count of the PICS array. Updating

images means that the developer will only have to import the images to the drawables folder and then

add the name of the image to the PICS array.

package puzzle.template;

/**

* A class to hold the static data used across the application.

*

* @author Rick

*/

public class Data {

/**

* The music track.

*/

public static int TRACK_01 = R.raw.track1;

/**

* List of images reference id's.

*/

public final static int[] PICS = {R.drawable.image4, R.drawable.image5,

R.drawable.image6};

}

PuzzleStatsFragment One of the other fragments that I use in the application, if not the only other one. This Fragment will be

displayed to the user when they click the stats icon “+” here they will be shown a list of their stats. All

stats collected are from the way the user has been interacting with the application. It shows a list of

every size of puzzle that has been completed as well as the record time for each puzzle size. I also

include counters for the link buttons and save media features.

newInstance() Create a new fragment using the newInstance() method to keep a level of separation from the class that

is using the fragment. This keep’s the actual instantiation of the fragment from being used out of the

point of use and increases decoupling. Passing arguments in this method, you can use a bundle objet to

store them. In case the activity is recreating the fragments, the data will persist. If you do override the

default constructor to take parameters, remember to make a default empty constructor to be called by

the Android framework. However, this is not recommended.

public static PuzzleStatsFragment newInstance() {

return new PuzzleStatsFragment();

}

onCreate() In the on create method we can allow for the fragment to add option to our menu. In this application we

have only one menu that the user can access from the Android framework. However, we want different

option to show depending on what fragment we are in.

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

//allow menu options to be added

setHasOptionsMenu(true);

setRetainInstance(true);

}

onCreateOptionsMenu() A menu can be inflated, meaning created from xml to objects, here with options that are meant to be

shown when this Fragment is visible.

@Override

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {

inflater.inflate(R.menu.main_stats, menu);

super.onCreateOptionsMenu(menu, inflater);

}

onCreateView() I create the root view from an xml sheet. We will be setting text into these views later. Once the root

view is inflated you can get a reference to all the child views in your xml sheet you defined. After getting

references for all the views that will need to be modified we can return the root view.

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedInstanceState) {

// Inflate the layout for this fragment

View view = inflater.inflate(R.layout.fragment_puzzle_stats, container,

false);

puzzlesSolvedTextView = (TextView)

view.findViewById(R.id.puzzlesSolvedCountTextView);

twoXtwoPuzzleSolvedCountTextView = (TextView)

view.findViewById(R.id.twoXtwoPuzzlesSolvedTextView);

fourRecordSolveTimeTextView = (TextView)

view.findViewById(R.id.twoXtwoSolveTimeTextView);

threeXthreePuzzleSolvedCountTextView = (TextView)

view.findViewById(R.id.threeXthreePuzzlesSolvedTextView);

nineRecordSolveTimeTextView = (TextView)

view.findViewById(R.id.threeXthreeSolveTimeTextView);

fourXfourPuzzleSolvedCountTextView = (TextView)

view.findViewById(R.id.fourXfourPuzzlesSolvedTextView);

sixteenRecordSolveTimeTextView = (TextView)

view.findViewById(R.id.fourXfourSolveTimeTextView);

fiveXfivePuzzleSolvedCountTextView = (TextView)

view.findViewById(R.id.fiveXfivePuzzlesSolvedTextView);

twentyFiveRecordSolveTimeTextView = (TextView)

view.findViewById(R.id.fiveXfiveSolveTimeTextView);

sixXsixPuzzleSolvedCountTextView = (TextView)

view.findViewById(R.id.sixXsixPuzzlesSolvedTextView);

thirtySixRecordSolveTimeTextView = (TextView)

view.findViewById(R.id.sixXsixSolveTimeTextView);

sevenXsevenPuzzleSolvedCountTextView = (TextView)

view.findViewById(R.id.sevenXsevenPuzzlesSovedTextView);

fourtySevenRecordSolveTimeTextView = (TextView)

view.findViewById(R.id.sevenXsevenSolveTimeTextView);

puzzlesSavedTextView = (TextView) view.findViewById(R.id.imageSavedTextView);

blogLinksTraversedTextView = (TextView)

view.findViewById(R.id.blogLinksTraversedTextView);

musicSavedTextView = (TextView) view.findViewById(R.id.musicSavedTextView);

return view;

}

onResume() The only work I add into the onResume method is the work to update our stats views. This will be talked

about more in the updatePuzzleStats() method.

@Override

public void onResume() {

super.onResume();

updatePuzzleStats();

}

updatePuzzleStats() The work to be done on views will need to be done in a thread that is run on the UI thread. In this

method we can set the texts to our view. The time related stats I divide the amount by 1000 if the root

variable is representing milliseconds. This way we can show a readable stat to the user in seconds.

public void updatePuzzleStats() {

Activity act = getActivity();

act.runOnUiThread(new Runnable() {

@Override

public void run() {

String temp = "" + commonVariables.puzzlesSolved;

if (puzzlesSolvedTextView != null)

puzzlesSolvedTextView.setText(temp);

temp = "" + commonVariables.fourPiecePuzzleSolvedCount;

if (twoXtwoPuzzleSolvedCountTextView != null)

twoXtwoPuzzleSolvedCountTextView.setText(temp);

temp = "" + commonVariables.fourRecordSolveTime / 1000.0 + " sec.";

if (fourRecordSolveTimeTextView != null)

fourRecordSolveTimeTextView.setText(temp);

temp = "" + commonVariables.ninePiecePuzzleSolvedCount;

if (threeXthreePuzzleSolvedCountTextView != null)

threeXthreePuzzleSolvedCountTextView.setText(temp);

temp = "" + commonVariables.nineRecordSolveTime / 1000.0 + " sec.";

if (nineRecordSolveTimeTextView != null)

nineRecordSolveTimeTextView.setText(temp);

temp = "" + commonVariables.sixteenPiecePuzzleSolvedCount;

if (fourXfourPuzzleSolvedCountTextView != null)

fourXfourPuzzleSolvedCountTextView.setText(temp);

temp = "" + commonVariables.sixteenRecordSolveTime / 1000.0 + " sec.";

if (sixteenRecordSolveTimeTextView != null)

sixteenRecordSolveTimeTextView.setText(temp);

temp = "" + commonVariables.twentyfivePiecePuzzleSolvedCount;

if (fiveXfivePuzzleSolvedCountTextView != null)

fiveXfivePuzzleSolvedCountTextView.setText(temp);

temp = "" + commonVariables.twentyFiveRecordSolveTime / 1000.0 + "

sec.";

if (twentyFiveRecordSolveTimeTextView != null)

twentyFiveRecordSolveTimeTextView.setText(temp);

temp = "" + commonVariables.thirtysixPiecePuzzleSolvedCount;

if (sixXsixPuzzleSolvedCountTextView != null)

sixXsixPuzzleSolvedCountTextView.setText(temp);

temp = "" + commonVariables.thirtySixRecordsSolveTime / 1000.0 + "

sec.";

if (thirtySixRecordSolveTimeTextView != null)

thirtySixRecordSolveTimeTextView.setText(temp);

temp = "" + commonVariables.fourtyninePiecePuzzleSolvedCount;

if (sevenXsevenPuzzleSolvedCountTextView != null)

sevenXsevenPuzzleSolvedCountTextView.setText(temp);

temp = "" + commonVariables.fourtyNineRecordsSolveTime / 1000.0 + "

sec.";

if (fourtySevenRecordSolveTimeTextView != null)

fourtySevenRecordSolveTimeTextView.setText(temp);

temp = "" + commonVariables.imagesSaved;

if (puzzlesSavedTextView != null) {

puzzlesSavedTextView.setText(temp);

}

temp = "" + commonVariables.blogLinksTraversed;

if (blogLinksTraversedTextView != null) {

blogLinksTraversedTextView.setText(temp);

}

temp = "" + commonVariables.musicSaved;

if (musicSavedTextView != null) {

musicSavedTextView.setText(temp);

}

}

});

}

SaveMusicService The save music service is used to save the music track to the user’s device. It’s mostly code provided by

Android from samples to save the image that will check that there is a valid directory and if it’s writable

and then finally save the track itself. After saving the track or notifying the user that the track is already

saved, or if there was an error, the user will be shown a toast message. Using a service means that the

code will be run in the background to completion and continue to run even if the application is closed.

SaveMusicService() We pass in a string in the call to super() to name the service, the documentation says this is only used in

debugging, but you can still make the name relevant to your usage.

public SaveMusicService() {

super("SaveMusicService");

}

onHandleIntent() When the thread that will be doing the work is called to work, it will be going through this method. At

this point we can call the method of saveMusicTrack() to perform the work of saving the track.

@Override

protected void onHandleIntent(Intent intent) {

try {

PendingIntent reply = intent.getParcelableExtra(PENDING_RESULT_ACTION);

if (intent != null) {

int x = saveMusicTrack();

switch (x){

case 0:

reply.send(ERROR);

case 1:

reply.send(SUCCESS);

case 2:

reply.send(EXISTS);

default:

}

}

} catch (PendingIntent.CanceledException pce) {

pce.printStackTrace();

}

}

serviceToast() Creating a Toast message can be done if we use a handler to post a runnable. In the run method I get

the context, a Service is itself a context so we can call for it from this class itself. Don’t forget to call

show() on your Toast for it to be displayed to the user.

private void serviceToast(final String message) {

new Handler(Looper.getMainLooper()).post(new Runnable() {

@Override

public void run() {

Toast.makeText(getApplicationContext(), message,

Toast.LENGTH_LONG).show();

}

});

}

saveMusicTrack() The work to save a music track is in this method. Keeping track of two boolean variables, we start to

check the device to see if we can write to it, storage must both be available and writable in order to save

a music track. If the file already exists we can notify the user and then exit. If we are ready to save we

can turn the track into a byte stream and send to the device. After successfully saving the track we can

update the counter and then the UI via a reply object. I also notify the user on the error conditions that

contributed.

private int saveMusicTrack() {

boolean mExternalStorageAvailable;

boolean mExternalStorageWriteable;

// save current image to devices images folder

String state = Environment.getExternalStorageState();

// check if writing is an option

if (Environment.MEDIA_MOUNTED.equals(state)) {

// We can read and write the media

mExternalStorageAvailable = mExternalStorageWriteable = true;

} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {

// We can only read the media

mExternalStorageAvailable = true;

mExternalStorageWriteable = 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 = mExternalStorageWriteable = false;

}

if (mExternalStorageAvailable && mExternalStorageWriteable) {

// then write picture to phone

File path = Environment

.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);

final String name = "Signal.mp3";

File file = new File(path, name);

MediaScannerConnection

.scanFile(

getApplicationContext(),

new String[]{file.toString()},

null,

new MediaScannerConnection.OnScanCompletedListener() {

@Override

public void onScanCompleted(

String path, Uri uri) {

}

});

// check for file in directory

if (file.exists()) {

serviceToast("Track Exists!");

return EXISTS;

} else {

try {

boolean b1 = path.mkdirs();

boolean b2 = path.exists();

// Make sure the Pictures directory exists.

if (b1 || b2) {

InputStream is =

getApplicationContext().getResources().openRawResource(

Data.TRACK_01);

OutputStream os = new FileOutputStream(file);

byte[] data = new byte[is.available()];

is.read(data);

os.write(data);

is.close();

os.close();

CommonVariables.getInstance().musicSaved++;

serviceToast("MP3 Saved!");

MediaScannerConnection

.scanFile(

getApplicationContext(),

new String[]{file.toString()},

null,

new

MediaScannerConnection.OnScanCompletedListener() {

@Override

public void onScanCompleted(

String path, Uri uri) {

}

});

} else {

serviceToast("Could not make/access directory.");

return ERROR;

}

} catch (IOException e) {

serviceToast("ERROR making/accessing directory.");

return ERROR;

}

}

} else {

serviceToast("Directory not available/writable.");

return ERROR;

}

return SUCCESS;

}

PuzzleSlot The puzzle slot class is the divided frame of the full image. In dividing the image into separate pieces we

need a way to track where every piece is going as we drag it around the screen. We can assign a piece to

each slot, this will be similar to the slot, in that it will have its corner coordinates saved in it but the

difference is the slots will not change its values. Slot 0 in the top left corner will always have the slot

number of 0. When moving a piece into a slot we also move the slot number into the piece number’s

slot. This will make it easier to see if we have a solved puzzle.

package puzzle.template.puzzle;

/**

* A class to hold the piece and coordinates of the puzzle piece frame as well

* as its own slot number in the whole divided image.

*

* @author Rick

*

*/

public class PuzzleSlot {

public int sx, sy, sx2, sy2;

public PuzzlePiece puzzlePiece;

public int slotNum;

public PuzzleSlot() {

puzzlePiece = new PuzzlePiece();

}

}

PuzzlePiece The puzzle piece object will hold the subsection bitmap and the current coordinates of the piece. Since

we will be moving the piece around the screen, we will be overwriting the four corner coordinates, and

on finally setting a piece we will be overwriting these coordinates. The slot that the piece is set into will

be used to set the new coordinates. Remember the slot doesn’t change its values only the piece.

package puzzle.template.puzzle;

import android.graphics.Bitmap;

/***

* A class to hold the puzzle piece bitmap, as well as its coordinates and

* number in the whole image.

*

* @author Rick

*/

public class PuzzlePiece {

public int px, py, px2, py2;

public Bitmap bitmap;

public int pieceNum;

}

Test Code This section will cover the test code I used for creating puzzles. It will cover using the menu to select a

puzzle and change the number of sides each time. This will show the puzzle is building and that the

device can handle switching between puzzle sizes and bitmaps without crashing.

ThreadingTests This class holds our tests for the puzzle. We need to get a reference to the menu in our fragment. I grab

a reference to the fragment’s from the activity by getting all fragments and checking its type. If you

aren’t using the support library then you can still find the Fragment by tag or id.

setUp() The set up method stores the references we need. We can use the Activity later as well as we use the

menu for setting up new puzzles. With a more complex application containing multiple fragments in a

single Activity, you can search by tag or id to find your fragment. I also have a reference to the menu

kept so keeping this reference will make it easier to test recreating a puzzle from the menu options.

@Override

protected void setUp() throws Exception {

super.setUp();

mainActivity = getActivity();

puzzleFragment = (PuzzleFragment)

mainActivity.getSupportFragmentManager().findFragmentByTag(PuzzleFragment.TAG);

if (puzzleFragment != null) {

puzzleSurface = puzzleFragment.puzzleSurface;

menu = puzzleFragment.menu;

}

}

NotNullTests I have a set of non-null tests that check for our stored references to have a value.

public void testMainActivityNotNull() {

assertNotNull("MainActivity is null", mainActivity);

}

public void testPuzzleFragmentNotNull() {

assertNotNull("PuzzleFragment is null", puzzleFragment);

}

public void testPuzzleSurfaceNotNull() {

assertNotNull("PuzzleSurface is null", puzzleSurface);

}

public void testMenuNotNull() {

assertNotNull("Menu is null", menu);

}

testMenuPuzzleRecreate() In this method we loop through and press the menu button then ok creating a new puzzle. I have this

loop set for 5 iterations. This means it will check for a new puzzle size each time. You can update this to

match the number of images in your Data pics class or a specific number like I have it.

public void testMenuPuzzleRecreate() {

for (int i = 0; i < IMAGE_TEST_COUNT; i++) {

getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);

getInstrumentation().waitForIdleSync();

getInstrumentation().runOnMainSync(new Runnable() {

@Override

public void run() {

menu.performIdentifierAction(

R.id.new_puzzle, 0);

}

});

getInstrumentation().waitForIdleSync();

getInstrumentation().runOnMainSync(new Runnable() {

@Override

public void run() {

puzzleSurface.dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();

}

});

getInstrumentation().waitForIdleSync();

//set the new puzzle size to be from 2 - 7

puzzleSurface.defaultPuzzleSize = "" + ((i % 6) + 2);

}

}

testMenuPuzzleRecreateAndCloseDefaultCreate() This method will create a puzzle by using the menu to open the new puzzle dialog and then use the

default value of 2 x 2 puzzle size.

public void testMenuPuzzleRecreateAndCloseDefaultCreate() {

getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);

getInstrumentation().waitForIdleSync();

getInstrumentation().runOnMainSync(new Runnable() {

@Override

public void run() {

menu.performIdentifierAction(

R.id.new_puzzle, 0);

}

});

getInstrumentation().waitForIdleSync();

getInstrumentation().runOnMainSync(new Runnable() {

@Override

public void run() {

puzzleSurface.dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();

}

});

getInstrumentation().waitForIdleSync();

}

RandomPuzzleTest I have created a test class to test that each new puzzle is created with a random slot order.

testMenuPuzzleRecreateAndCloseDefaultCreate In this section I use the menu to recreate a puzzle and then check that every piece is in a different slot

than what it should be.

public void testMenuPuzzleRecreateAndCloseDefaultCreate() {

for (int ii = 0; ii < IMAGE_TEST_COUNT; ii++) {

getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);

getInstrumentation().waitForIdleSync();

getInstrumentation().runOnMainSync(new Runnable() {

@Override

public void run() {

menu.performIdentifierAction(

R.id.new_puzzle, 0);

}

});

getInstrumentation().waitForIdleSync();

getInstrumentation().waitForIdleSync();

getInstrumentation().runOnMainSync(new Runnable() {

@Override

public void run() {

puzzleSurface.dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();

}

});

getInstrumentation().waitForIdleSync();

solo.waitForView(R.id.puzzle);

for (int i = 0; i < puzzleSurface.common.puzzleSlots.length; i++) {

int piece = puzzleSurface.common.puzzleSlots[i].puzzlePiece.pieceNum;

int slot = puzzleSurface.common.puzzleSlots[i].slotNum;

assertNotSame(piece, slot);

}

}

}

Attribution DISCLAIMER: The sample code described herein is provided on an "as is" basis, without warranty of any

kind, to the fullest extent permitted by law. MobileApplications009 does not warrant or guarantee the

individual success developers may have in implementing the sample code on their development

platforms or in using their own IDE, all samples were developed in Android Studio 2.2.2

MobileApplications009 does not warrant, guarantee or make any representations regarding the use,

results of use, accuracy, timeliness or completeness of any data or information relating to the sample

code. MobileApplications009 disclaims all warranties, express or implied, and in particular, disclaims all

warranties of merchantability, fitness for a particular purpose, and warranties related to the code, or

any service or software related thereto.

MobileApplications009 shall not be liable for any direct, indirect or consequential damages or costs of

any type arising out of any action taken by you or others related to the sample code.

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.

Thanks very much,

Richard A. Perez

MobileApplications009

[email protected]