<|start_filename|>src/com/jakewharton/tronwallpaper/Preferences.java<|end_filename|>
package com.jakewharton.tronwallpaper;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.DialogInterface.OnClickListener;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.preference.Preference.OnPreferenceClickListener;
import android.provider.MediaStore;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import com.jakewharton.tronwallpaper.R;
import com.jakewharton.utilities.WidgetLocationsPreference;

/**
 * Settings activity for SnakeWallpaper
 * 
 * @author <NAME>
 */
public class Preferences extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
	/**
	 * SharedPreference name.
	 */
	/*package*/static final String SHARED_NAME = "TronWallpaper";
	
	/**
	 * Filename of the change log.
	 */
	private static final String FILENAME_CHANGE_LOG = "changelog.html";
	
	/**
	 * Filename of the credits.
	 */
	private static final String FILENAME_CREDITS = "credits.html";
	
	/**
	 * Filename of the instructions.
	 */
	private static final String FILENAME_INSTRUCTIONS = "instructions.html";
	
	/**
	 * Filename of the to do list.
	 */
	private static final String FILENAME_TODO = "todo.html";
	
	/**
	 * Select background activity callback ID.
	 */
	private static final int SELECT_BACKGROUND = 1;
	
	
	
    @Override
    protected void onCreate(final Bundle icicle) {
        super.onCreate(icicle);
        
        final PreferenceManager manager = this.getPreferenceManager();
        manager.setSharedPreferencesName(Preferences.SHARED_NAME);
        this.addPreferencesFromResource(R.xml.preferences);
        
        final SharedPreferences preferences = manager.getSharedPreferences();
        final Resources resources = this.getResources();
        
        //reset display and game
        this.findPreference(resources.getString(R.string.settings_display_reset_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() {
			public boolean onPreferenceClick(final Preference preference) {
				(new AlertDialog.Builder(Preferences.this))
					.setMessage(resources.getString(R.string.reset_display))
					.setCancelable(false)
					.setPositiveButton(resources.getString(R.string.yes), new DialogInterface.OnClickListener() {
						public void onClick(final DialogInterface dialog, final int which) {
							Preferences.this.loadDisplayAndGameDefaults();
							
							Toast.makeText(Preferences.this, resources.getString(R.string.reset_display_toast), Toast.LENGTH_LONG).show();
						}
					})
					.setNegativeButton(resources.getString(R.string.no), null)
					.show();
				return true;
			}
		});
        
        //reset colors
        this.findPreference(resources.getString(R.string.settings_color_reset_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() {
			public boolean onPreferenceClick(final Preference preference) {
				(new AlertDialog.Builder(Preferences.this))
					.setMessage(resources.getString(R.string.reset_color))
					.setCancelable(false)
					.setPositiveButton(resources.getString(R.string.yes), new DialogInterface.OnClickListener() {
						public void onClick(final DialogInterface dialog, final int which) {
							Preferences.this.loadColorDefaults();
							
							Toast.makeText(Preferences.this, resources.getString(R.string.reset_color_toast), Toast.LENGTH_LONG).show();
						}
					})
					.setNegativeButton(resources.getString(R.string.no), null)
					.show();
				return true;
			}
		});
        
        //info email
        this.findPreference(resources.getString(R.string.information_contact_email_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() {
			public boolean onPreferenceClick(final Preference preference) {
				Preferences.this.infoEmail();
				return true;
			}
		});
        
        //info twitter
        this.findPreference(resources.getString(R.string.information_contact_twitter_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() {
			public boolean onPreferenceClick(final Preference preference) {
				Preferences.this.infoTwitter();
				return true;
			}
		});
        
        //info web
        this.findPreference(resources.getString(R.string.information_contact_website_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() {
			public boolean onPreferenceClick(final Preference preference) {
				Preferences.this.infoWeb();
				return true;
			}
		});
        
        //info market
        this.findPreference(resources.getString(R.string.information_market_view_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() {
			public boolean onPreferenceClick(final Preference preference) {
				Preferences.this.infoMarket();
				return true;
			}
		});
        
        //instructions
        this.findPreference(resources.getString(R.string.instructions_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() {
			public boolean onPreferenceClick(final Preference preference) {
				Preferences.this.viewInstructions();
				return true;
			}
		});
        
        //change log
        this.findPreference(resources.getString(R.string.changelog_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() {
			public boolean onPreferenceClick(final Preference preference) {
				Preferences.this.viewChangelog();
				return true;
			}
		});
        
        //credits
        this.findPreference(resources.getString(R.string.credits_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() {
			public boolean onPreferenceClick(final Preference preference) {
				Preferences.this.viewCredits();
				return true;
			}
		});
        
        //todo
        this.findPreference(resources.getString(R.string.todo_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() {
			public boolean onPreferenceClick(final Preference preference) {
				Preferences.this.viewTodo();
				return true;
			}
		});
        
        //github
        this.findPreference(resources.getString(R.string.github_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() {
			public boolean onPreferenceClick(Preference preference) {
				Preferences.this.viewGitHub();
				return true;
			}
		});
        
        //xda
        this.findPreference(resources.getString(R.string.xda_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() {
			public boolean onPreferenceClick(Preference preference) {
				Preferences.this.viewXda();
				return true;
			}
		});
        
        //background image
        this.findPreference(resources.getString(R.string.settings_color_bgimage_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() {
			public boolean onPreferenceClick(final Preference preference) {
				Preferences.this.startActivityForResult(new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI), Preferences.SELECT_BACKGROUND);
				return true;
			}
		});
        
        //clear background image
        this.findPreference(resources.getString(R.string.settings_color_bgimageclear_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() {
			public boolean onPreferenceClick(final Preference preference) {
				Preferences.this.getPreferenceManager().getSharedPreferences().edit().putString(resources.getString(R.string.settings_color_bgimage_key), null).commit();
				Toast.makeText(Preferences.this, R.string.settings_color_bgimageclear_toast, Toast.LENGTH_SHORT).show();
				return true;
			}
		});

        //Register as a preference change listener
        Wallpaper.PREFERENCES.registerOnSharedPreferenceChangeListener(this);
        this.onSharedPreferenceChanged(Wallpaper.PREFERENCES, null);
        
        //Check previously installed version
        final int thisVersion = resources.getInteger(R.integer.version_code);
        final int defaultVersion = resources.getInteger(R.integer.version_code_default);
        final int previousVersion = preferences.getInt(resources.getString(R.string.version_code_key), defaultVersion);
        if (previousVersion == defaultVersion) {
        	//First install
        	
        	//Store this version
        	this.getPreferenceManager().getSharedPreferences().edit().putInt(resources.getString(R.string.version_code_key), thisVersion).commit();
        	//Show hello
        	(new AlertDialog.Builder(this))
        		.setTitle(resources.getString(R.string.title))
        		.setMessage(resources.getString(R.string.welcome_firstrun))
        		.setCancelable(true)
        		.setPositiveButton(resources.getString(R.string.yes), new OnClickListener() {
					public void onClick(final DialogInterface dialog, final int which) {
						Preferences.this.viewInstructions();
					}
				})
				.setNegativeButton(resources.getString(R.string.no), null)
				.show();
        } else if (previousVersion < thisVersion) {
        	//First run after upgrade
        	
        	//Store this version
        	this.getPreferenceManager().getSharedPreferences().edit().putInt(resources.getString(R.string.version_code_key), thisVersion).commit();
        	//Show hello
        	(new AlertDialog.Builder(this))
        		.setTitle(resources.getString(R.string.title))
        		.setMessage(resources.getString(R.string.welcome_upgrade))
        		.setCancelable(true)
        		.setPositiveButton(resources.getString(R.string.yes), new OnClickListener() {
					public void onClick(final DialogInterface dialog, final int which) {
						Preferences.this.viewChangelog();
					}
				})
				.setNegativeButton(resources.getString(R.string.no), null)
				.show();
        }
    }
    
    /**
     * Open change log.
     */
    private void viewChangelog() {
		final Intent intent = new Intent(this, About.class);
		intent.putExtra(About.EXTRA_FILENAME, Preferences.FILENAME_CHANGE_LOG);
		intent.putExtra(About.EXTRA_TITLE, this.getResources().getString(R.string.changelog_title));
		this.startActivity(intent);
    }
    
    /**
     * Open instructions.
     */
    private void viewInstructions() {
		final Intent intent = new Intent(this, About.class);
		intent.putExtra(About.EXTRA_FILENAME, Preferences.FILENAME_INSTRUCTIONS);
		intent.putExtra(About.EXTRA_TITLE, this.getResources().getString(R.string.instructions_title));
		this.startActivity(intent);
    }
    
    /**
     * Open credits
     */
    private void viewCredits() {
		final Intent intent = new Intent(this, About.class);
		intent.putExtra(About.EXTRA_FILENAME, Preferences.FILENAME_CREDITS);
		intent.putExtra(About.EXTRA_TITLE, this.getResources().getString(R.string.credits_title));
		this.startActivity(intent);
    }
    
    /**
     * Open todo
     */
    private void viewTodo() {
		final Intent intent = new Intent(this, About.class);
		intent.putExtra(About.EXTRA_FILENAME, Preferences.FILENAME_TODO);
		intent.putExtra(About.EXTRA_TITLE, this.getResources().getString(R.string.todo_title));
		this.startActivity(intent);
    }
    
    /**
     * Open GitHub
     */
    private void viewGitHub() {
    	final Intent intent = new Intent(Intent.ACTION_VIEW);
		intent.setData(Uri.parse(this.getResources().getString(R.string.github_href)));
		
		this.startActivity(intent);
    }
    
    /**
     * Open XDA
     */
    private void viewXda() {
    	final Intent intent = new Intent(Intent.ACTION_VIEW);
		intent.setData(Uri.parse(this.getResources().getString(R.string.xda_href)));
		
		this.startActivity(intent);
    }

	public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
		final boolean all = (key == null);
		final Resources resources = this.getResources();
		
		//Only enable clear bg image when a bg image is set
		final String bgimage = resources.getString(R.string.settings_color_bgimage_key);
		if (all || key.equals(bgimage)) {
			final boolean enabled = preferences.getString(bgimage, null) != null;
			this.findPreference(resources.getString(R.string.settings_color_bgimageclear_key)).setEnabled(enabled);
			this.findPreference(resources.getString(R.string.settings_color_bgopacity_key)).setEnabled(enabled);
		}
		
		//If the icon rows or cols are explicitly changed then clear the widget locations
		final String iconRows = resources.getString(R.string.settings_display_iconrows_key);
		final String iconCols = resources.getString(R.string.settings_display_iconcols_key);
		if (all || key.equals(iconRows) || key.equals(iconCols)) {
			final int rows = preferences.getInt(iconRows, resources.getInteger(R.integer.display_iconrows_default));
			final int cols = preferences.getInt(iconCols, resources.getInteger(R.integer.display_iconcols_default));
			final String widgetLocations = resources.getString(R.string.settings_display_widgetlocations_key);
			
			if (!all) {
				//Clear any layouts
				preferences.edit().putString(widgetLocations, resources.getString(R.string.display_widgetlocations_default)).commit();
			}
			
			//Update with counts
			((WidgetLocationsPreference)this.findPreference(widgetLocations)).setIconCounts(rows, cols);
		}
    }

    @Override
	public boolean onCreateOptionsMenu(Menu menu) {
		this.getMenuInflater().inflate(R.menu.preferences, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		final Resources resources = this.getResources();
		
		switch (item.getItemId()) {
			case R.id.menu_reset:
				(new AlertDialog.Builder(this))
					.setMessage(resources.getString(R.string.reset_all))
					.setCancelable(false)
					.setPositiveButton(resources.getString(R.string.yes), new DialogInterface.OnClickListener() {
						public void onClick(DialogInterface dialog, int which) {
							Preferences.this.loadDisplayAndGameDefaults();
							Preferences.this.loadColorDefaults();
							
							Toast.makeText(Preferences.this, resources.getString(R.string.reset_all_toast), Toast.LENGTH_LONG).show();
						}
					})
					.setNegativeButton(resources.getString(R.string.no), null)
					.show();
				return true;
				
			default:
				return super.onOptionsItemSelected(item);
		}
	}

	@Override
	protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
		if (resultCode == Activity.RESULT_OK) {
			final Resources resources = this.getResources();
			
			switch (requestCode) {
				case Preferences.SELECT_BACKGROUND:
					//Store the string value of the background image
				    this.getPreferenceManager().getSharedPreferences().edit().putString(resources.getString(R.string.settings_color_bgimage_key), data.getDataString()).commit();
				    Toast.makeText(this, R.string.settings_color_bgimage_toast, Toast.LENGTH_SHORT).show();
					break;
					
				default:
					super.onActivityResult(requestCode, resultCode, data);
			}
		}
	}

	/**
	 * Launch an intent to send an email.
	 */
	private void infoEmail() {
        final Resources resources = this.getResources();
		final Intent intent = new Intent(Intent.ACTION_SEND);
		intent.setType("plain/text");
		intent.putExtra(Intent.EXTRA_EMAIL, new String[] { resources.getString(R.string.information_contact_email_data) });
		intent.putExtra(Intent.EXTRA_SUBJECT, resources.getString(R.string.title));
		
		this.startActivity(intent);
    }
    
	/**
	 * Launch an intent to view twitter page.
	 */
    private void infoTwitter() {
        final Resources resources = this.getResources();
		final Intent intent = new Intent(Intent.ACTION_VIEW);
		intent.setData(Uri.parse(resources.getString(R.string.information_contact_twitter_data)));
		
		this.startActivity(intent);
    }
    
    /**
     * Launch an intent to view website.
     */
    private void infoWeb() {
        final Resources resources = this.getResources();
    	final Intent intent = new Intent(Intent.ACTION_VIEW);
		intent.setData(Uri.parse(resources.getString(R.string.information_contact_website_data)));
		
		this.startActivity(intent);
    }
    
    /**
     * Launch an intent to view other market applications.
     */
    private void infoMarket()
    {
        final Resources resources = this.getResources();
    	final Intent intent = new Intent(Intent.ACTION_VIEW);
		intent.setData(Uri.parse(resources.getString(R.string.information_market_view_data)));
		
		this.startActivity(intent);
    }
    
    /**
     * Reset display preferences to their defaults.
     */
    private void loadDisplayAndGameDefaults() {
        final Resources resources = this.getResources();
	    final SharedPreferences.Editor editor = Preferences.this.getPreferenceManager().getSharedPreferences().edit();

		//user controllable
		editor.remove(resources.getString(R.string.settings_game_usercontrol_key));
		//fps
		editor.remove(resources.getString(R.string.settings_display_fps_key));
		//show walls
		editor.remove(resources.getString(R.string.settings_display_showwalls_key));
		//icon rows
		editor.remove(resources.getString(R.string.settings_display_iconrows_key));
		//icon cols
		editor.remove(resources.getString(R.string.settings_display_iconcols_key));
		//icon row spacing
		editor.remove(resources.getString(R.string.settings_display_rowspacing_key));
		//icon col spacing
		editor.remove(resources.getString(R.string.settings_display_colspacing_key));
		//widget locations
		editor.remove(resources.getString(R.string.settings_display_widgetlocations_key));
		//padding top
		editor.remove(resources.getString(R.string.settings_display_padding_top_key));
		//padding bottom
		editor.remove(resources.getString(R.string.settings_display_padding_bottom_key));
		//padding left
		editor.remove(resources.getString(R.string.settings_display_padding_left_key));
		//padding right
		editor.remove(resources.getString(R.string.settings_display_padding_right_key));
	
		editor.commit();
    }
    
    /**
     * Reset color preferences to their defaults.
     */
    private void loadColorDefaults() {
        final Resources resources = this.getResources();
		final SharedPreferences.Editor editor = Preferences.this.getPreferenceManager().getSharedPreferences().edit();

		//background
		editor.remove(resources.getString(R.string.settings_color_background_key));
		//walls
		editor.remove(resources.getString(R.string.settings_color_walls_key));
		//background image
		editor.remove(resources.getString(R.string.settings_color_bgimage_key));
		//background opacity
		editor.remove(resources.getString(R.string.settings_color_bgopacity_key));
		//player
		editor.remove(resources.getString(R.string.settings_color_player_key));
		//opponent
		editor.remove(resources.getString(R.string.settings_color_opponent_key));
		
		editor.commit();
    }
}


<|start_filename|>src/com/jakewharton/utilities/IntegerListPreference.java<|end_filename|>
/*
 * Copyright 2010 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.jakewharton.utilities;

import android.content.Context;
import android.preference.ListPreference;
import android.util.AttributeSet;

/**
 * A list preference which persists its values as integers instead of strings.
 * Code reading the values should use
 * {@link android.content.SharedPreferences#getInt}.
 * When using XML-declared arrays for entry values, the arrays should be regular
 * string arrays containing valid integer values.
 *
 * @author <NAME>
 */
public class IntegerListPreference extends ListPreference {
	/**
	 * Create a new instance of the IntegerListPreference.
	 * 
	 * @param context Context.
	 */
	public IntegerListPreference(final Context context) {
		super(context);
		
		this.verifyEntryValues(null);
	}

	/**
	 * Create a new instnace of the IntegerListPreference.
	 * 
	 * @param context Context.
	 * @param attrs Attributes.
	 */
	public IntegerListPreference(final Context context, final AttributeSet attrs) {
		super(context, attrs);

		this.verifyEntryValues(null);
	}

	
	
	@Override
	public void setEntryValues(final CharSequence[] entryValues) {
		final CharSequence[] oldValues = getEntryValues();
		super.setEntryValues(entryValues);
		this.verifyEntryValues(oldValues);
	}

	@Override
	public void setEntryValues(final int entryValuesResId) {
		final CharSequence[] oldValues = getEntryValues();
		super.setEntryValues(entryValuesResId);
		this.verifyEntryValues(oldValues);
	}

	@Override
	protected String getPersistedString(final String defaultReturnValue) {
		//During initial load, there's no known default value
		int defaultIntegerValue = Integer.MIN_VALUE;
		if (defaultReturnValue != null) {
			defaultIntegerValue = Integer.parseInt(defaultReturnValue);
		}

		// When the list preference asks us to read a string, instead read an integer.
		int value = this.getPersistedInt(defaultIntegerValue);
		return Integer.toString(value);
	}

	@Override
	protected boolean persistString(final String value) {
		// When asked to save a string, instead save an integer
		return this.persistInt(Integer.parseInt(value));
	}

	/**
	 * Verify all of the values in the list.
	 * 
	 * @param oldValues Old value.
	 */
	private void verifyEntryValues(final CharSequence[] oldValues) {
		final CharSequence[] entryValues = this.getEntryValues();
		if (entryValues == null) {
			return;
		}

		for (final CharSequence entryValue : entryValues) {
			try {
				Integer.parseInt(entryValue.toString());
			} catch (NumberFormatException nfe) {
				super.setEntryValues(oldValues);
				throw nfe;
			}
		}
	}
}

<|start_filename|>src/com/jakewharton/utilities/NumberPreference.java<|end_filename|>
package com.jakewharton.utilities;

import com.jakewharton.tronwallpaper.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;

/**
 * Preference which displays a seek bar for the selection of an integer.
 * 
 * @author <NAME>
 */
public class NumberPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener {
	/**
	 * The seek bar.
	 */
	private SeekBar mSeekBar;
	
	/**
	 * Representation of the value.
	 */
	private TextView mValueText;
	
	/**
	 * A suffix to append to the value for display.
	 */
	private String mSuffix;
	
	/**
	 * Maximum value.
	 */
	private int mMax;
	
	/**
	 * Minimum value.
	 */
	private int mMin;
	
	/**
	 * The current value.
	 */
	private int mValue = 0;

	
	
	/**
	 * Create a new instance of the NumberPreference.
	 * 
	 * @param context Context.
	 * @param attrs Attributes.
	 */
	public NumberPreference(final Context context, final AttributeSet attrs) {
		super(context, attrs);
		this.setPersistent(true);

		final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NumberPreference, 0, 0);
		this.mSuffix = a.getString(R.styleable.NumberPreference_suffix);
		this.mMin = a.getInt(R.styleable.NumberPreference_min, 0);
		this.mMax = a.getInt(R.styleable.NumberPreference_max, 100);

		this.setDialogLayoutResource(R.layout.number_preference);
	}

	
	
	@Override
	protected void onBindDialogView(final View view) {
		super.onBindDialogView(view);
		
		((TextView)view.findViewById(R.id.dialogMessage)).setText(this.getDialogMessage());

		this.mValueText = (TextView)view.findViewById(R.id.actualValue);

		this.mSeekBar = (SeekBar)view.findViewById(R.id.myBar);
		this.mSeekBar.setOnSeekBarChangeListener(this);
		this.mSeekBar.setMax(this.mMax - this.mMin);
		this.mSeekBar.setProgress(this.mValue - this.mMin);

		final String t = String.valueOf(this.mValue);
		this.mValueText.setText(this.mSuffix == null ? t : t.concat(this.mSuffix));
	}

	@Override
	protected Object onGetDefaultValue(final TypedArray a, final int index) {
		return a.getInt(index, 0);
	}

	@Override
	protected void onSetInitialValue(final boolean restore, final Object defaultValue) {
		this.mValue = this.getPersistedInt(defaultValue == null ? 0 : (Integer)defaultValue);
	}

	@Override
	protected void onDialogClosed(final boolean positiveResult) {
		super.onDialogClosed(positiveResult);

		if (positiveResult) {
			final int value = this.mSeekBar.getProgress() + this.mMin;
			if (this.callChangeListener(value)) {
				this.saveValue(value);
			}
		}
	}

	/**
	 * Set the value.
	 * 
	 * @param value Value.
	 */
	public void setValue(int value) {
		if (value > this.mMax) {
			value = this.mMax;
		} else if (value < this.mMin) {
			value = this.mMin;
		}
		this.mValue = value;
	}
	
	/**
	 * Set and persist the value.
	 * 
	 * @param value Value.
	 */
	private void saveValue(final int value) {
		this.setValue(value);
		this.persistInt(value);
	}

	/**
	 * Set the maximum possible value.
	 * 
	 * @param max Maximum value.
	 */
	public void setMax(final int max) {
		this.mMax = max;
		if (this.mValue > this.mMax) {
			this.setValue(this.mMax);
		}
	}

	/**
	 * Set the minimum possible value.
	 * 
	 * @param min Minimum value.
	 */
	public void setMin(final int min) {
		if (min < this.mMax) {
			this.mMin = min;
		}
	}

	/**
	 * Called when the seek bar value is changed.
	 */
	public void onProgressChanged(final SeekBar seek, final int value, final boolean fromTouch) {
		final String t = String.valueOf(value + this.mMin);
		this.mValueText.setText(this.mSuffix == null ? t : t.concat(this.mSuffix));
	}

	/**
	 * Called when the seek bar has started to changed. Not used.
	 */
	public void onStartTrackingTouch(final SeekBar seek) {}
	
	/**
	 * Called when the seek bar has stopped changing. Not used.
	 */
	public void onStopTrackingTouch(final SeekBar seek) {}
}

<|start_filename|>src/com/jakewharton/utilities/ColorPreference.java<|end_filename|>
package com.jakewharton.utilities;

import com.jakewharton.tronwallpaper.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.SurfaceView;
import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.SeekBar.OnSeekBarChangeListener;

/**
 * Preference for picking a color which is persisted as an integer.
 * 
 * @author <NAME>
 */
public class ColorPreference extends DialogPreference {
	/**
	 * Since we do not allow changing the alpha value, always use the maximum value of 255.
	 */
	private static final int ALPHA = 0xff;
	
	
	
	/**
	 * Color preview at top of dialog.
	 */
	private SurfaceView mPreview;
	
	/**
	 * Seek bar for the red color part.
	 */
	private SeekBar mR;
	
	/**
	 * Seek bar for the green color part.
	 */
	private SeekBar mG;
	
	/**
	 * Seek bar for the blue color part.
	 */
	private SeekBar mB;
	
	/**
	 * Value of the red seek bar.
	 */
	private TextView mRValue;
	
	/**
	 * Value of the green seek bar.
	 */
	private TextView mGValue;
	
	/**
	 * Value of the blue seek bar.
	 */
	private TextView mBValue;
	
	/**
	 * The color.
	 */
	private int mColor;
	
	/**
	 * Temporary color storage used for callback.
	 */
	private Integer mTempColor;
	
	/**
	 * Listener for any of the seek bar value changes.
	 */
	private final OnSeekBarChangeListener mSeekBarChangeListener = new OnSeekBarChangeListener() {
		public void onStopTrackingTouch(SeekBar seekBar) {}
		public void onStartTrackingTouch(SeekBar seekBar) {}
		public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
			final int red = ColorPreference.this.mR.getProgress();
			final int green = ColorPreference.this.mG.getProgress();
			final int blue = ColorPreference.this.mB.getProgress();
			final int color = Color.argb(ColorPreference.ALPHA, red, green, blue);
			
			ColorPreference.this.mRValue.setText(Integer.toString(red));
			ColorPreference.this.mGValue.setText(Integer.toString(green));
			ColorPreference.this.mBValue.setText(Integer.toString(blue));
			
			ColorPreference.this.setValue(color);
		}
	};

	
	
	/**
	 * Create a new instance of the ColorPreference.
	 * 
	 * @param context Context.
	 * @param attrs Attributes.
	 */
	public ColorPreference(final Context context, final AttributeSet attrs) {
		super(context, attrs);
		
		this.setPersistent(true);
		this.setDialogLayoutResource(R.layout.color_preference);
	}
	
	
	
	@Override
	protected void onBindDialogView(final View view) {
		super.onBindDialogView(view);
		
		this.mPreview = (SurfaceView)view.findViewById(R.id.preview);
		this.mPreview.setBackgroundColor(this.mColor);
		
		this.mR = (SeekBar)view.findViewById(R.id.red);
		this.mR.setProgress(Color.red(this.mColor));
		this.mR.setOnSeekBarChangeListener(this.mSeekBarChangeListener);
		this.mG = (SeekBar)view.findViewById(R.id.green);
		this.mG.setProgress(Color.green(this.mColor));
		this.mG.setOnSeekBarChangeListener(this.mSeekBarChangeListener);
		this.mB = (SeekBar)view.findViewById(R.id.blue);
		this.mB.setProgress(Color.blue(this.mColor));
		this.mB.setOnSeekBarChangeListener(this.mSeekBarChangeListener);
		
		this.mRValue = (TextView)view.findViewById(R.id.red_value);
		this.mRValue.setText(Integer.toString(Color.red(this.mColor)));
		this.mGValue = (TextView)view.findViewById(R.id.green_value);
		this.mGValue.setText(Integer.toString(Color.green(this.mColor)));
		this.mBValue = (TextView)view.findViewById(R.id.blue_value);
		this.mBValue.setText(Integer.toString(Color.blue(this.mColor)));
	}

	@Override
	protected Object onGetDefaultValue(final TypedArray a, final int index) {
		return a.getInt(index, 0);
	}

	@Override
	protected void onSetInitialValue(final boolean restore, final Object defaultValue) {
		final int color = this.getPersistedInt(defaultValue == null ? 0 : (Integer)defaultValue);
		this.mColor = color;
	}

	@Override
	protected void onDialogClosed(final boolean positiveResult) {
		super.onDialogClosed(positiveResult);

		if (positiveResult) {
			this.mTempColor = this.mColor;
			if (this.callChangeListener(this.mTempColor)) {
				this.saveValue(this.mTempColor);
			}
		}
	}

	/**
	 * Set the value of the color and update the preview.
	 * 
	 * @param color Color value.
	 */
	public void setValue(final int color) {
		this.mColor = color;
		this.mPreview.setBackgroundColor(color);
	}
	
	/**
	 * Set and persist the value of the color.
	 * 
	 * @param color Color value.
	 */
	public void saveValue(final int color) {
		this.setValue(color);
		this.persistInt(color);
	}
}

<|start_filename|>src/com/jakewharton/tronwallpaper/Game.java<|end_filename|>
package com.jakewharton.tronwallpaper;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import com.jakewharton.tronwallpaper.R;
import com.jakewharton.utilities.WidgetLocationsPreference;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;

/**
 * Tron game.
 * 
 * @author <NAME>
 */
public class Game implements SharedPreferences.OnSharedPreferenceChangeListener {
	/**
	 * Possible directions of travel.
	 * 
	 * @author <NAME>
	 */
	enum Direction {
		NORTH, SOUTH, EAST, WEST;
		
		
		
		/**
		 * Get the direction that is the opposite of this one.
		 * 
		 * @return Opposite direction.
		 */
		public Game.Direction getOpposite() {
			switch (this) {
				case NORTH:
					return Game.Direction.SOUTH;
				case SOUTH:
					return Game.Direction.NORTH;
				case EAST:
					return Game.Direction.WEST;
				case WEST:
					return Game.Direction.EAST;
				default:
					throw new IllegalStateException("This is impossible.");
			}
		}
	}
	
	
	
	/**
	 * Single random number generate for this wallpaper.
	 */
	/*package*/static final Random RANDOM = new Random();
	
	/**
	 * Tag used for logging.
	 */
	private static final String TAG = "TronWallpaper.Game";
	
	/**
	 * Cell value for a wall.
	 */
	private static final boolean CELL_WALL = false;
	
	/**
	 * Cell value for a blank space.
	 */
	private static final boolean CELL_BLANK = true;
	
	/**
	 * Block cells between icon rows.
	 */
	private static final int CELLS_BETWEEN_ROW = 4;
	
	/**
	 * Block cells between icon columns.
	 */
	private static final int CELLS_BETWEEN_COLUMN = 4;
	
	/**
	 * If a random number [0, RANDOM_TURN_MULTIPLIER) is 0 then randomly turn the light cycle.
	 */
	private static final int RANDOM_TURN_MULTIPLIER = 250;
	

	
	/**
	 * Number of cells on the board horizontally.
	 */
	private int mCellsWide;
	
	/**
	 * Number of cells on the board vertically.
	 */
	private int mCellsTall;
	
	/**
	 * Number of cells horizontally between the columns.
	 */
	private int mCellColumnSpacing;
	
	/**
	 * Number of cells vertically between the rows.
	 */
	private int mCellRowSpacing;
	
	/**
	 * Width (in pixels) of a single cell.
	 */
	private float mScaleX;
	
	/**
	 * Height (in pixels) of a single cell.
	 */
	private float mScaleY;
	
	/**
	 * Height (in pixels) of the screen.
	 */
    private int mScreenHeight;
    
    /**
     * Width (in pixels) of the screen.
     */
    private int mScreenWidth;
    
    /**
     * Whether or not the screen is currently in landscape mode.
     */
    private boolean mIsLandscape;
    
    /**
     * Number of icon rows on the launcher.
     */
    private int mIconRows;
    
    /**
     * Number of icon columns on the launcher.
     */
    private int mIconCols;
    
    /**
     * 2-dimensional array of the board's cells.
     */
	private boolean[][] mBoard;
    
    /**
     * Color of the background.
     */
    private int mGameBackground;
    
    /**
     * Top padding (in pixels) of the grid from the screen top.
     */
    private float mDotGridPaddingTop;
    
    /**
     * Left padding (in pixels) of the grid from the screen left.
     */
    private float mDotGridPaddingLeft;
    
    /**
     * Bottom padding (in pixels) of the grid from the screen bottom.
     */
    private float mDotGridPaddingBottom;
    
    /**
     * Right padding (in pixels) of the grid from the screen right.
     */
    private float mDotGridPaddingRight;
    
    /**
     * Path to the user background image (if any).
     */
    private String mBackgroundPath;
    
    /**
     * The user background image (if any).
     */
    private Bitmap mBackground;
    
    /**
     * The locations of widgets on the launcher.
     */
    private List<Rect> mWidgetLocations;
    
    /**
     * Paint to draw the background color.
     */
    private final Paint mBackgroundPaint;
    
    /**
     * Walls foreground color.
     */
    private final Paint mWallsForeground;
    
    /**
     * Whether or not we are displaying icon walls
     */
    private boolean mIsDisplayingWalls;
    
    /**
     * Player cycle positions
     */
    private final LinkedList<Point> mPlayer;
    
    /**
     * Opponent cycle positions
     */
    private final LinkedList<Point> mOpponent;
    
    /**
     * Player cycle direction.
     */
    private Game.Direction mDirectionPlayer;
    
    /**
     * Opponent cycle direction.
     */
    private Game.Direction mDirectionOpponent;
    
    /**
     * Direction the user wants us to travel in.
     */
    private Game.Direction mWantsToGo;
    
    /**
     * Opponent cycle color.
     */
    private final Paint mOpponentForeground;
    
    /**
     * Player cycle color.
     */
    private final Paint mPlayerForeground;
    
    /**
     * Precalculated wall rectangles for drawing
     */
    private final List<RectF> mWalls;
    
    
    
    /**
     * Create a new game.
     */
    public Game() {
    	if (Wallpaper.LOG_VERBOSE) {
    		Log.v(Game.TAG, "> Game()");
    	}

        //Create Paints
    	this.mWallsForeground = new Paint(Paint.ANTI_ALIAS_FLAG);
    	this.mWallsForeground.setStyle(Paint.Style.STROKE);
        this.mBackgroundPaint = new Paint();
        this.mOpponentForeground = new Paint();
        this.mPlayerForeground = new Paint();
        
        this.mPlayer = new LinkedList<Point>();
        this.mOpponent = new LinkedList<Point>();
        this.mWalls = new LinkedList<RectF>();
        
        //Load all preferences or their defaults
        Wallpaper.PREFERENCES.registerOnSharedPreferenceChangeListener(this);
        this.onSharedPreferenceChanged(Wallpaper.PREFERENCES, null);

    	if (Wallpaper.LOG_VERBOSE) {
    		Log.v(Game.TAG, "< Game()");
    	}
    }

    
    
    /**
     * Handle the changing of a preference.
     */
	public void onSharedPreferenceChanged(final SharedPreferences preferences, final String key) {
    	if (Wallpaper.LOG_VERBOSE) {
    		Log.v(Game.TAG, "> onSharedPreferenceChanged()");
    	}
    	
		final boolean all = (key == null);
		final Resources resources = Wallpaper.CONTEXT.getResources();
		
		boolean hasLayoutChanged = false;
		boolean hasGraphicsChanged = false;
		
		final String showWalls = resources.getString(R.string.settings_display_showwalls_key);
		if (all || key.equals(showWalls)) {
			this.mIsDisplayingWalls = preferences.getBoolean(showWalls, resources.getBoolean(R.bool.display_showwalls_default));
			
			if (Wallpaper.LOG_DEBUG) {
				Log.d(Game.TAG, "Is Displaying Walls: " + this.mIsDisplayingWalls);
			}
		}
		
		final String widgetLocations = resources.getString(R.string.settings_display_widgetlocations_key);
		if (all || key.equals(widgetLocations)) {
			this.mWidgetLocations = WidgetLocationsPreference.convertStringToWidgetList(preferences.getString(widgetLocations, resources.getString(R.string.display_widgetlocations_default)));
			hasLayoutChanged = true;
			
			if (Wallpaper.LOG_DEBUG) {
				Log.d(Game.TAG, "Widget Locations: " + (this.mWidgetLocations.size() / 4));
			}
		}
		
		
		// COLORS //
        
		final String gameBackground = resources.getString(R.string.settings_color_background_key);
		if (all || key.equals(gameBackground)) {
			this.mGameBackground = preferences.getInt(gameBackground, resources.getInteger(R.integer.color_background_default));
			
			if (Wallpaper.LOG_DEBUG) {
				Log.d(Game.TAG, "Background: #" + Integer.toHexString(this.mGameBackground));
			}
		}
		
		final String wallsForeground = resources.getString(R.string.settings_color_walls_key);
		if (all || key.equals(wallsForeground)) {
			this.mWallsForeground.setColor(preferences.getInt(wallsForeground, resources.getInteger(R.integer.color_walls_default)));
			
			if (Wallpaper.LOG_DEBUG) {
				Log.d(Game.TAG, "Walls Foreground: #" + Integer.toHexString(this.mWallsForeground.getColor()));
			}
		}
		
		final String backgroundImage = resources.getString(R.string.settings_color_bgimage_key);
		if (all || key.equals(backgroundImage)) {
			this.mBackgroundPath = preferences.getString(backgroundImage, null);
			
			if (this.mBackgroundPath != null) {			
				if (Wallpaper.LOG_DEBUG) {
					Log.d(Game.TAG, "Background Image: " + this.mBackgroundPath);
				}
				
				//Trigger performResize
				hasGraphicsChanged = true;
			} else {
				this.mBackground = null;
			}
		}
		
		final String backgroundOpacity = resources.getString(R.string.settings_color_bgopacity_key);
		if (all || key.equals(backgroundOpacity)) {
			this.mBackgroundPaint.setAlpha(preferences.getInt(backgroundOpacity, resources.getInteger(R.integer.color_bgopacity_default)));
			
			if (Wallpaper.LOG_DEBUG) {
				Log.d(Game.TAG, "Background Image Opacity: " + this.mBackgroundPaint.getAlpha());
			}
		}
		
		final String lightCycle = resources.getString(R.string.settings_color_player_key);
		if (all || key.equals(lightCycle)) {
			this.mPlayerForeground.setColor(preferences.getInt(lightCycle, resources.getInteger(R.integer.color_player_default)));
			
			if (Wallpaper.LOG_DEBUG) {
				Log.d(Game.TAG, "Player Foreground: #" + Integer.toHexString(this.mPlayerForeground.getColor()));
			}
		}
		
		final String opponent = resources.getString(R.string.settings_color_opponent_key);
		if (all || key.equals(opponent)) {
			this.mOpponentForeground.setColor(preferences.getInt(opponent, resources.getInteger(R.integer.color_opponent_default)));
			
			if (Wallpaper.LOG_DEBUG) {
				Log.d(Game.TAG, "Opponent Foreground: #" + Integer.toHexString(this.mOpponentForeground.getColor()));
			}
		}
    	
        
		// GRID //
		
		final String dotGridPaddingLeft = resources.getString(R.string.settings_display_padding_left_key);
		if (all || key.equals(dotGridPaddingLeft)) {
			this.mDotGridPaddingLeft = preferences.getInt(dotGridPaddingLeft, resources.getInteger(R.integer.display_padding_left_default));
			hasGraphicsChanged = true;
			
			if (Wallpaper.LOG_DEBUG) {
				Log.d(Game.TAG, "Dot Grid Padding Left: " + this.mDotGridPaddingLeft);
			}
		}

		final String dotGridPaddingRight = resources.getString(R.string.settings_display_padding_right_key);
		if (all || key.equals(dotGridPaddingRight)) {
			this.mDotGridPaddingRight = preferences.getInt(dotGridPaddingRight, resources.getInteger(R.integer.display_padding_right_default));
			hasGraphicsChanged = true;
			
			if (Wallpaper.LOG_DEBUG) {
				Log.d(Game.TAG, "Dot Grid Padding Right: " + this.mDotGridPaddingRight);
			}
		}

		final String dotGridPaddingTop = resources.getString(R.string.settings_display_padding_top_key);
		if (all || key.equals(dotGridPaddingTop)) {
			this.mDotGridPaddingTop = preferences.getInt(dotGridPaddingTop, resources.getInteger(R.integer.display_padding_top_default));
			hasGraphicsChanged = true;
			
			if (Wallpaper.LOG_DEBUG) {
				Log.d(Game.TAG, "Dot Grid Padding Top: " + this.mDotGridPaddingTop);
			}
		}

		final String dotGridPaddingBottom = resources.getString(R.string.settings_display_padding_bottom_key);
		if (all || key.equals(dotGridPaddingBottom)) {
			this.mDotGridPaddingBottom = preferences.getInt(dotGridPaddingBottom, resources.getInteger(R.integer.display_padding_bottom_default));
			hasGraphicsChanged = true;
			
			if (Wallpaper.LOG_DEBUG) {
				Log.d(Game.TAG, "Dot Grid Padding Bottom: " + this.mDotGridPaddingBottom);
			}
		}
		
		
		// CELLS //
		
		final String iconRows = resources.getString(R.string.settings_display_iconrows_key);
		if (all || key.equals(iconRows)) {
			this.mIconRows = preferences.getInt(iconRows, resources.getInteger(R.integer.display_iconrows_default));
			hasLayoutChanged = true;
			
			if (Wallpaper.LOG_DEBUG) {
				Log.d(Game.TAG, "Icon Rows: " + this.mIconRows);
			}
		}
		
		final String iconCols = resources.getString(R.string.settings_display_iconcols_key);
		if (all || key.equals(iconCols)) {
			this.mIconCols = preferences.getInt(iconCols, resources.getInteger(R.integer.display_iconcols_default));
			hasLayoutChanged = true;
			
			if (Wallpaper.LOG_DEBUG) {
				Log.d(Game.TAG, "Icon Cols: " + this.mIconCols);
			}
		}
		
		final String cellSpacingRow = resources.getString(R.string.settings_display_rowspacing_key);
		if (all || key.equals(cellSpacingRow)) {
			this.mCellRowSpacing = preferences.getInt(cellSpacingRow, resources.getInteger(R.integer.display_rowspacing_default));
			hasLayoutChanged = true;
			
			if (Wallpaper.LOG_DEBUG) {
		    	Log.d(Game.TAG, "Cell Row Spacing: " + this.mCellRowSpacing);
			}
		}
		
		final String cellSpacingCol = resources.getString(R.string.settings_display_colspacing_key);
		if (all || key.equals(cellSpacingCol)) {
			this.mCellColumnSpacing = preferences.getInt(cellSpacingCol, resources.getInteger(R.integer.display_colspacing_default));
			hasLayoutChanged = true;
			
			if (Wallpaper.LOG_DEBUG) {
		    	Log.d(Game.TAG, "Cell Column Spacing: " + this.mCellColumnSpacing);
			}
		}
		
		if (hasLayoutChanged) {
	    	this.mCellsWide = (this.mIconCols * (this.mCellColumnSpacing + Game.CELLS_BETWEEN_COLUMN)) + Game.CELLS_BETWEEN_COLUMN;
	    	this.mCellsTall = (this.mIconRows * (this.mCellRowSpacing + Game.CELLS_BETWEEN_ROW)) + Game.CELLS_BETWEEN_ROW;
	    	
	    	if (Wallpaper.LOG_DEBUG) {
	    		Log.d(Game.TAG, "Cells Wide: " + this.mCellsWide);
	    		Log.d(Game.TAG, "Cells Tall: " + this.mCellsTall);
	    	}
	    	
	    	//Create playing board
	        this.mBoard = new boolean[this.mCellsTall][this.mCellsWide];
		}
		if (hasLayoutChanged || hasGraphicsChanged) {
			if ((this.mScreenWidth > 0) && (this.mScreenHeight > 0)) {
				//Resize everything to fit
				this.performResize(this.mScreenWidth, this.mScreenHeight);
			}

	    	this.newBoard();
		}

    	if (Wallpaper.LOG_VERBOSE) {
    		Log.v(Game.TAG, "< onSharedPreferenceChanged()");
    	}
	}

    /**
     * Test if a Point is a valid coordinate on the game board.
     * 
     * @param position Point representing coordinate.
     * @return Boolean indicating whether or not the position is valid.
     */
	private boolean isValidPosition(final Point position) {
		return ((position.x >= 0) && (position.x < this.mCellsWide)
				&& (position.y >= 0) && (position.y < this.mCellsTall)
				&& (this.mBoard[position.y][position.x] == Game.CELL_BLANK));
	}
    
    /**
     * Reset the game state to that of first initialization.
     */
    public void newBoard() {
    	if (Wallpaper.LOG_VERBOSE) {
    		Log.v(Game.TAG, "> newBoard()");
    	}

    	//Initialize board
    	final int iconCellsWidth = this.mCellColumnSpacing + Game.CELLS_BETWEEN_COLUMN;
    	final int iconCellsHeight = this.mCellRowSpacing + Game.CELLS_BETWEEN_ROW;
    	for (int y = 0; y < this.mCellsTall; y++) {
    		for (int x = 0; x < this.mCellsWide; x++) {
    			this.mBoard[y][x] = (((x % iconCellsWidth) < Game.CELLS_BETWEEN_COLUMN) || ((y % iconCellsHeight) < Game.CELLS_BETWEEN_ROW)) ? Game.CELL_BLANK : Game.CELL_WALL;
    		}
    	}
    	
    	//Remove board under widgets
    	for (final Rect widget : this.mWidgetLocations) {
    		if (Wallpaper.LOG_DEBUG) {
    			Log.d(Game.TAG, "Widget: L=" + widget.left + ", T=" + widget.top + ", R=" + widget.right + ", B=" + widget.bottom);
    		}
    		
    		final int left = (widget.left * iconCellsWidth) + Game.CELLS_BETWEEN_COLUMN;
    		final int top = (widget.top * iconCellsHeight) + Game.CELLS_BETWEEN_ROW;
    		final int right = (widget.right * iconCellsWidth) + iconCellsWidth - 1;
    		final int bottom = (widget.bottom * iconCellsHeight) + iconCellsHeight - 1;
    		for (int y = top; y <= bottom; y++) {
    			for (int x = left; x <= right; x++) {
    				this.mBoard[y][x] = Game.CELL_WALL;
    			}
    		}
    	}
    	
    	//Initialize game
    	this.newGame();
    	
    	if (Wallpaper.LOG_VERBOSE) {
    		Log.v(Game.TAG, "< newBoard()");
    	}
    }
    
    /**
     * Reset players.
     */
    private void newGame() {
    	//Create player and opponent
    	this.mPlayer.clear();
    	this.mOpponent.clear();
    	
    	//Get starting position for players
    	this.mPlayer.add(this.getRandomValidPosition());
    	this.mOpponent.add(this.getRandomValidPosition());
    	
    	//No user direction
    	this.mWantsToGo = null;
    }
    
    /**
     * Get a random valid position on the board.
     * 
     * @return Point.
     */
    private Point getRandomValidPosition() {
    	final Point position = new Point();
    	while (true) {
    		position.x = Game.RANDOM.nextInt(this.mCellsWide);
    		position.y = Game.RANDOM.nextInt(this.mCellsTall);
    		if (!this.isCollision(position)) {
    			return position;
    		}
    	}
    }
    
    /**
     * Set the user desired direction.
     * 
     * @param direction
     */
    public void setWantsToGo(final Game.Direction direction) {
    	this.mWantsToGo = direction;
    	
    	if (Wallpaper.LOG_DEBUG) {
    		Log.d(Game.TAG, "Wants To Go: " + direction.toString());
    	}
    }
    
    /**
     * Iterate player and opponent.
     */
    public void tick() {
    	this.determineNextPlayerDirection();
    	final Point playerNewPoint = Game.move(this.mPlayer.getLast(), this.mDirectionPlayer);
    	if (this.isCollision(playerNewPoint)) {
    		this.newGame();
    		return;
    	}
    	this.mPlayer.add(playerNewPoint);
    	
    	this.determineNextOpponentDirection();
    	final Point opponentNewPoint = Game.move(this.mOpponent.getLast(), this.mDirectionOpponent);
    	if (this.isCollision(opponentNewPoint)) {
    		this.newGame();
    		return;
    	}
    	this.mOpponent.add(opponentNewPoint);
    }
    
    /**
     * Test if a point collides with the player or opponent.
     * 
     * @param testPoint Point to test.
     * @return Whether or not the point collides.
     */
    private boolean isCollision(final Point testPoint) {
    	//Test for walls
    	if (!this.isValidPosition(testPoint)) {
    		return true;
    	}
    	//Test opponent first
    	for (final Point point : this.mOpponent) {
    		if (Game.pointEquals(point, testPoint)) {
    			return true;
    		}
    	}
    	//Test player
    	for (final Point point : this.mPlayer) {
    		if (Game.pointEquals(point, testPoint)) {
    			return true;
    		}
    	}
    	return false;
    }
    
    /**
     * Try to go straight, otherwise make a valid turn and, on occasion, randomize!
     */
    private void determineNextPlayerDirection() {
		final Point head = this.mPlayer.getLast();
		
		//Try the user direction first
		final Point newPoint = Game.move(head, this.mWantsToGo);
		if ((this.mWantsToGo != null) && !this.isCollision(newPoint)) {
			//Follow user direction and GTFO
			this.mDirectionPlayer = this.mWantsToGo;
			return;
		}
		
    	//TODO: real AI
		Game.Direction nextDirection = null;
    	//favor current direction most of the time
    	if (!this.isCollision(Game.move(head, this.mDirectionPlayer)) && (Game.RANDOM.nextInt(Game.RANDOM_TURN_MULTIPLIER) != 0)) {
    		nextDirection = this.mDirectionPlayer;
    	} else {
	    	final List<Game.Direction> directions = new LinkedList<Game.Direction>();
	    	for (final Game.Direction direction : Game.Direction.values()) {
	    		if (!this.isCollision(Game.move(head, direction))) {
	    			directions.add(direction);
	    		}
	    	}
	    	if (directions.size() == 0) {
	    		nextDirection = Game.Direction.NORTH;
	    	} else {
	    		nextDirection = directions.get(Game.RANDOM.nextInt(directions.size()));
	    	}
    	}
		
		//If the wants-to-go direction exists and the AI forced us to change direction then wants-to-go direction
		//is impossible and should be cleared
		if ((this.mWantsToGo != null) && (this.mDirectionPlayer != nextDirection)) {
			this.mWantsToGo = null;
			
			if (Wallpaper.LOG_DEBUG) {
				Log.d(Game.TAG, "Clearing wants-to-go direction via AI.");
			}
		}
		
		this.mDirectionPlayer = nextDirection;
    }
    
    /**
     * Try to go straight, otherwise make a valid turn and, on occasion, randomize!
     */
    private void determineNextOpponentDirection() {
    	//TODO: real AI
    	final Point position = this.mOpponent.getLast();
    	//favor current direction most of the time
    	if (!this.isCollision(Game.move(position, this.mDirectionOpponent)) && (Game.RANDOM.nextInt(Game.RANDOM_TURN_MULTIPLIER) != 0)) {
    		return;
    	}
    	final List<Game.Direction> directions = new LinkedList<Game.Direction>();
    	for (final Game.Direction direction : Game.Direction.values()) {
    		if (!this.isCollision(Game.move(position, direction))) {
    			directions.add(direction);
    		}
    	}
    	if (directions.size() == 0) {
    		this.mDirectionOpponent = Game.Direction.NORTH;
    	} else {
    		this.mDirectionOpponent = directions.get(Game.RANDOM.nextInt(directions.size()));
    	}
    }

    /**
     * Resize the game board and all entities according to a new width and height.
     * 
     * @param screenWidth New width.
     * @param screenHeight New height.
     */
    public void performResize(int screenWidth, int screenHeight) {
    	if (Wallpaper.LOG_VERBOSE) {
    		Log.v(Game.TAG, "> performResize(width = " + screenWidth + ", height = " + screenHeight + ")");
    	}
    	
    	//Background image
    	if (this.mBackgroundPath != null) {
			try {
				final Bitmap temp = BitmapFactory.decodeStream(Wallpaper.CONTEXT.getContentResolver().openInputStream(Uri.parse(this.mBackgroundPath)));
				final float pictureAR = temp.getWidth() / (temp.getHeight() * 1.0f);
				final float screenAR = screenWidth / (screenHeight * 1.0f);
				int newWidth;
				int newHeight;
				int x;
				int y;
				
				if (pictureAR > screenAR) {
					//wider than tall related to the screen AR
					newHeight = screenHeight;
					newWidth = (int)(temp.getWidth() * (screenHeight / (temp.getHeight() * 1.0f)));
					x = (newWidth - screenWidth) / 2;
					y = 0;
				} else {
					//taller than wide related to the screen AR
					newWidth = screenWidth;
					newHeight = (int)(temp.getHeight() * (screenWidth / (temp.getWidth() * 1.0f)));
					x = 0;
					y = (newHeight - screenHeight) / 2;
				}
				
	    		final Bitmap scaled = Bitmap.createScaledBitmap(temp, newWidth, newHeight, false);
	    		this.mBackground = Bitmap.createBitmap(scaled, x, y, screenWidth, screenHeight);
			} catch (final Exception e) {
				e.printStackTrace();
				Log.w(Game.TAG, "Unable to load background bitmap.");
				Toast.makeText(Wallpaper.CONTEXT, "Unable to load background bitmap.", Toast.LENGTH_SHORT).show();
				this.mBackground = null;
			}
    	}
    	
    	this.mIsLandscape = (screenWidth > screenHeight);
    	this.mScreenWidth = screenWidth;
    	this.mScreenHeight = screenHeight;
    	
    	if (this.mIsLandscape) {
    		this.mScaleX = (screenWidth - (this.mDotGridPaddingLeft + this.mDotGridPaddingRight + this.mDotGridPaddingBottom)) / (this.mCellsWide * 1.0f);
    		this.mScaleY = (screenHeight - this.mDotGridPaddingTop) / (this.mCellsTall * 1.0f);
    	} else {
    		this.mScaleX = (screenWidth - (this.mDotGridPaddingLeft + this.mDotGridPaddingRight)) / (this.mCellsWide * 1.0f);
    		this.mScaleY = (screenHeight - (this.mDotGridPaddingTop + this.mDotGridPaddingBottom)) / (this.mCellsTall * 1.0f);
    	}
    	
    	//Calculate walls
    	this.mWalls.clear();
		
    	//Widget walls
    	for (final Rect widget : this.mWidgetLocations) {
			final float left = (widget.left * (this.mCellColumnSpacing + Game.CELLS_BETWEEN_COLUMN)) + Game.CELLS_BETWEEN_COLUMN;
			final float top = (widget.top * (this.mCellRowSpacing + Game.CELLS_BETWEEN_ROW)) + Game.CELLS_BETWEEN_ROW;
    		final float right = ((widget.right * (this.mCellColumnSpacing + Game.CELLS_BETWEEN_COLUMN)) + this.mCellColumnSpacing + Game.CELLS_BETWEEN_COLUMN);
    		final float bottom = ((widget.bottom * (this.mCellRowSpacing + Game.CELLS_BETWEEN_ROW)) + this.mCellRowSpacing + Game.CELLS_BETWEEN_ROW);
			
			this.mWalls.add(new RectF(left, top, right, bottom));
    	}
		
    	//Icon walls
    	for (int y = 0; y < this.mIconRows; y++) {
    		for (int x = 0; x < this.mIconCols; x++) {
    			boolean contained = false;
    			for (final Rect widget : this.mWidgetLocations) {
    				if (x >= widget.left && x <= widget.right && y >= widget.top && y <= widget.bottom) {
    					contained = true;
    					break;
    				}
    			}
    			if (contained) {
    				continue;
    			}
    			
    			final float left = (x * (this.mCellColumnSpacing + Game.CELLS_BETWEEN_COLUMN)) + Game.CELLS_BETWEEN_COLUMN;
    			final float top = (y * (this.mCellRowSpacing + Game.CELLS_BETWEEN_ROW)) + Game.CELLS_BETWEEN_ROW;
    			final float right = left + this.mCellColumnSpacing;
    			final float bottom = top + this.mCellRowSpacing;

    			this.mWalls.add(new RectF(left, top, right, bottom));
    		}
    	}
    	
    	if (Wallpaper.LOG_DEBUG) {
    		Log.d(Game.TAG, "Is Landscape: " + this.mIsLandscape);
    		Log.d(Game.TAG, "Screen Width: " + screenWidth);
    		Log.d(Game.TAG, "Screen Height: " + screenHeight);
    		Log.d(Game.TAG, "Scale X: " + this.mScaleX);
    		Log.d(Game.TAG, "Scale Y: " + this.mScaleY);
    	}

    	if (Wallpaper.LOG_VERBOSE) {
    		Log.v(Game.TAG, "< performResize()");
    	}
    }
    
    /**
     * Render the board and all entities on a Canvas.
     * 
     * @param c Canvas to draw on.
     */
    public void draw(final Canvas c) {
    	c.save();
    	
    	//Clear the screen in case of transparency in the image
		c.drawColor(this.mGameBackground);
    	if (this.mBackground != null) {
    		//Bitmap should already be sized to the screen so draw it at the origin
    		c.drawBitmap(this.mBackground, 0, 0, this.mBackgroundPaint);
    	}
        
        c.translate(this.mDotGridPaddingLeft, this.mDotGridPaddingTop);
        c.scale(this.mScaleX, this.mScaleY);
        
        //Draw dots and walls
        this.drawGameBoard(c);
        
        c.restore();
    }

    /**
     * Render the dots and walls.
     * 
     * @param c Canvas to draw on.
     */
    private void drawGameBoard(final Canvas c) {
    	//draw light cycle
    	for (final Point position : this.mPlayer) {
    		c.drawRect(position.x, position.y, position.x + 1, position.y + 1, this.mPlayerForeground);
    	}
    	//draw opponent
    	for (final Point position : this.mOpponent) {
    		c.drawRect(position.x, position.y, position.x + 1, position.y + 1, this.mOpponentForeground);
    	}
    	
        //draw walls if enabled
        if (this.mIsDisplayingWalls) {
        	for (final RectF wall : this.mWalls) {
        		c.drawRect(wall, this.mWallsForeground);
        	}
        }
    }

    

	/**
	 * Update the point one step in the direction specified.
	 * 
	 * @param point Point of original coordinates.
	 * @param direction Direction in which to move the point.
	 * @return New point coordinates.
	 */
    private static Point move(final Point point, final Game.Direction direction) {
    	final Point newPoint = new Point(point);
    	if (direction != null) {
	    	switch (direction) {
	    		case NORTH:
	    			newPoint.y -= 1;
					break;
					
	    		case SOUTH:
	    			newPoint.y += 1;
					break;
					
	    		case WEST:
	    			newPoint.x -= 1;
					break;
					
	    		case EAST:
	    			newPoint.x += 1;
					break;
	    	}
    	}
    	return newPoint;
    }
    
    /**
     * Determine whether two points represent the same coordinate.
     * 
     * @param one Point one.
     * @param two Point two.
     * @return Boolean.
     */
    private static boolean pointEquals(final Point one, final Point two) {
    	return ((one.x == two.x) && (one.y == two.y));
    }
}
