├── .gitignore ├── res ├── drawable │ ├── email.png │ ├── thumb.jpg │ ├── twitter.png │ └── world.png ├── drawable-hdpi │ ├── icon.png │ └── ic_menu_revert.png ├── drawable-mdpi │ ├── icon.png │ └── ic_menu_revert.png ├── values │ ├── bools.xml │ ├── attrs.xml │ ├── arrays.xml │ ├── integers.xml │ └── strings.xml ├── menu │ └── preferences.xml ├── xml │ ├── wallpaper.xml │ └── preferences.xml └── layout │ ├── number_preference.xml │ ├── icon_preference.xml │ └── color_preference.xml ├── default.properties ├── assets ├── credits.html ├── todo.html ├── changelog.html └── instructions.html ├── AndroidManifest.xml ├── README.md ├── src └── com │ └── jakewharton │ ├── breakoutwallpaper │ ├── About.java │ ├── Ball.java │ ├── Picker.java │ ├── Wallpaper.java │ ├── Preferences.java │ └── Game.java │ └── utilities │ ├── IconPreference.java │ ├── IntegerListPreference.java │ ├── NumberPreference.java │ ├── ColorPreference.java │ └── WidgetLocationsPreference.java └── LICENSE.txt /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | gen 3 | .project 4 | .classpath 5 | .settings 6 | -------------------------------------------------------------------------------- /res/drawable/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/BreakoutWallpaper/HEAD/res/drawable/email.png -------------------------------------------------------------------------------- /res/drawable/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/BreakoutWallpaper/HEAD/res/drawable/thumb.jpg -------------------------------------------------------------------------------- /res/drawable/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/BreakoutWallpaper/HEAD/res/drawable/twitter.png -------------------------------------------------------------------------------- /res/drawable/world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/BreakoutWallpaper/HEAD/res/drawable/world.png -------------------------------------------------------------------------------- /res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/BreakoutWallpaper/HEAD/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/BreakoutWallpaper/HEAD/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_menu_revert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/BreakoutWallpaper/HEAD/res/drawable-hdpi/ic_menu_revert.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_menu_revert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/BreakoutWallpaper/HEAD/res/drawable-mdpi/ic_menu_revert.png -------------------------------------------------------------------------------- /res/values/bools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | true 6 | -------------------------------------------------------------------------------- /res/menu/preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /res/xml/wallpaper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /default.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "build.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-7 12 | -------------------------------------------------------------------------------- /res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fill (solid shapes) 6 | Stroke (outlines) 7 | 8 | 9 | 0 10 | 1 11 | 12 | 13 | 14 | Endless 15 | Levels 16 | 17 | 18 | 0 19 | 1 20 | 21 | -------------------------------------------------------------------------------- /assets/credits.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 17 |
18 |

Your name could be here!

19 |

Go to the GitHub project and contribute!

20 | 21 |

Thanks

22 | 27 |
28 | 29 | -------------------------------------------------------------------------------- /assets/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 |

To Do / Requests

15 |

The following is a list of things which still need implemented. If you have something you would like to add please send an email to jakewharton@gmail.com.

16 | 17 |

Accepted

18 | 21 | 22 |

Pending

23 | 26 | 27 |

Rejected

28 | 31 | 32 | -------------------------------------------------------------------------------- /assets/changelog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 |
15 |

1.0.3 (In Development)

16 |

Bug Fixes:

17 | 21 |
22 |
23 |

1.0.2 (2010-09-17)

24 |

Bug Fixes:

25 | 29 |
30 |
31 |

1.0.1 (2010-09-06)

32 |

Bug Fixes:

33 | 36 |
37 |
38 |

1.0.0 (2010-09-03)

39 |

Initial release. 40 |

41 | 42 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 35 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Breakout Wallpaper 2 | ================== 3 | 4 | An Android live wallpaper which plays the brick-busting game of breakout around your icons. 5 | 6 | Market link: [tinyurl.com/27lwrp4](http://tinyurl.com/27lwrp4) or search "Breakout Wallpaper" 7 | 8 | 9 | Screenshots 10 | ----------- 11 | 12 | [![Screenshot 1](http://img31.imageshack.us/img31/9880/27940837.th.png)](http://img31.imageshack.us/img31/9880/27940837.png) 13 | [![Screenshot 2](http://img64.imageshack.us/img64/972/34920146.th.png)](http://img64.imageshack.us/img64/972/34920146.png) 14 | [![Screenshot 3](http://img718.imageshack.us/img718/5962/39401991.th.png)](http://img718.imageshack.us/img718/5962/39401991.png) 15 | 16 | 17 | **Follow me on [Twitter](http://twitter.com/JakeWharton/) for updates.** 18 | 19 | Please report all bugs to rather than rating negatively in the app comments. Enjoy! 20 | 21 | [![Click to download](http://chart.apis.google.com/chart?cht=qr&chs=200x200&chl=market://search%3Fq%3Dpname:com.jakewharton.breakoutwallpaper)](http://tinyurl.com/27lwrp4) 22 | 23 | 24 | License 25 | ======= 26 | 27 | Copyright 2010 Jake Wharton 28 | 29 | Licensed under the Apache License, Version 2.0 (the "License"); 30 | you may not use this file except in compliance with the License. 31 | You may obtain a copy of the License at 32 | 33 | http://www.apache.org/licenses/LICENSE-2.0 34 | 35 | Unless required by applicable law or agreed to in writing, software 36 | distributed under the License is distributed on an "AS IS" BASIS, 37 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 38 | See the License for the specific language governing permissions and 39 | limitations under the License. 40 | -------------------------------------------------------------------------------- /res/layout/number_preference.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 19 | 27 | 33 | 41 | 42 | -------------------------------------------------------------------------------- /src/com/jakewharton/breakoutwallpaper/About.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.breakoutwallpaper; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import android.app.Activity; 7 | import android.net.Uri; 8 | import android.os.Bundle; 9 | import android.webkit.WebView; 10 | 11 | /** 12 | * Activity which displays a web view with the contents loaded from an asset. 13 | * 14 | * @author Jake Wharton 15 | */ 16 | public class About extends Activity { 17 | /** 18 | * Filename of the asset to load. 19 | */ 20 | public static final String EXTRA_FILENAME = "filename"; 21 | 22 | /** 23 | * Title of the activity. 24 | */ 25 | public static final String EXTRA_TITLE = "title"; 26 | 27 | /** 28 | * Newline character to use between asset lines. 29 | */ 30 | private static final char NEWLINE = '\n'; 31 | 32 | /** 33 | * Error message displayed when the asset fails to load. 34 | */ 35 | private static final String ERROR = "Failed to load the file from assets."; 36 | 37 | /** 38 | * Encoding of the assets. 39 | */ 40 | private static final String MIME_TYPE = "text/html"; 41 | 42 | /** 43 | * Character set of the assets. 44 | */ 45 | private static final String ENCODING = "utf-8"; 46 | 47 | 48 | 49 | @Override 50 | protected void onCreate(final Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | 53 | final StringBuffer content = new StringBuffer(); 54 | 55 | try { 56 | //Load entire about plain text from asset 57 | final BufferedReader about = new BufferedReader(new InputStreamReader(this.getAssets().open(this.getIntent().getStringExtra(About.EXTRA_FILENAME)))); 58 | String data; 59 | while ((data = about.readLine()) != null) { 60 | content.append(data); 61 | content.append(About.NEWLINE); 62 | } 63 | } catch (IOException e) { 64 | e.printStackTrace(); 65 | content.append(About.ERROR); 66 | } 67 | 68 | this.setTitle(this.getIntent().getStringExtra(About.EXTRA_TITLE)); 69 | 70 | //Put text into layout 71 | final WebView view = new WebView(this); 72 | view.loadData(Uri.encode(content.toString()), About.MIME_TYPE, About.ENCODING); 73 | 74 | this.setContentView(view); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /res/layout/icon_preference.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | 21 | 29 | 36 | 44 | 52 | 60 | 61 | -------------------------------------------------------------------------------- /res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4 5 | 0 6 | 7 | 8 | 0 9 | 10 | 45 11 | 30 12 | 70 13 | 14 | 4 15 | 1 16 | 4 17 | 18 | 19 | 20 20 | 10 21 | 30 22 | 23 | -5 24 | -15 25 | 15 26 | 27 | -5 28 | -15 29 | 15 30 | 31 | 35 32 | -15 33 | 75 34 | 35 | 75 36 | -15 37 | 115 38 | 39 | 4 40 | 3 41 | 8 42 | 43 | 4 44 | 3 45 | 8 46 | 47 | 15 48 | 10 49 | 20 50 | 51 | 6 52 | 4 53 | 8 54 | 55 | 56 | 0xff3d3d3d 57 | 0xff 58 | 0x00 59 | 0xff 60 | 0xffecece2 61 | 0xffb55757 62 | 0xff76b865 63 | 0xff6a7bd4 64 | 0 65 | 0 66 | -------------------------------------------------------------------------------- /res/layout/color_preference.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 20 | 24 | 25 | 26 | 28 | 33 | 39 | 44 | 45 | 46 | 48 | 53 | 59 | 64 | 65 | 66 | 68 | 73 | 79 | 84 | 85 | -------------------------------------------------------------------------------- /src/com/jakewharton/utilities/IconPreference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jakewharton.utilities; 18 | 19 | import com.jakewharton.breakoutwallpaper.R; 20 | import android.content.Context; 21 | import android.graphics.drawable.Drawable; 22 | import android.preference.Preference; 23 | import android.util.AttributeSet; 24 | import android.view.View; 25 | import android.widget.ImageView; 26 | 27 | /** 28 | * A simple Preference which displays an icon next to the text. 29 | * 30 | * @author Jake Wharton 31 | */ 32 | public class IconPreference extends Preference { 33 | /** 34 | * The icon. 35 | */ 36 | private Drawable mIcon; 37 | 38 | 39 | 40 | /** 41 | * Create a new instance of the IconPreference. 42 | * 43 | * @param context Context. 44 | * @param attrs Attributes. 45 | */ 46 | public IconPreference(final Context context, final AttributeSet attrs) { 47 | this(context, attrs, 0); 48 | } 49 | 50 | /** 51 | * Create a new instance of the IconPreference. 52 | * 53 | * @param context Context. 54 | * @param attrs Attributes. 55 | * @param defStyle Style. 56 | */ 57 | public IconPreference(final Context context, final AttributeSet attrs, final int defStyle) { 58 | super(context, attrs, defStyle); 59 | 60 | this.setLayoutResource(R.layout.icon_preference); 61 | 62 | this.mIcon = context.obtainStyledAttributes(attrs, R.styleable.IconPreference, defStyle, 0).getDrawable(R.styleable.IconPreference_icon); 63 | } 64 | 65 | 66 | 67 | @Override 68 | public void onBindView(final View view) { 69 | super.onBindView(view); 70 | 71 | final ImageView imageView = (ImageView)view.findViewById(R.id.icon); 72 | if ((imageView != null) && (this.mIcon != null)) { 73 | imageView.setImageDrawable(this.mIcon); 74 | } 75 | } 76 | 77 | /** 78 | * Sets the icon for this Preference with a Drawable. 79 | * 80 | * @param icon The icon for this Preference 81 | */ 82 | public void setIcon(final Drawable icon) { 83 | if (((icon == null) && (this.mIcon != null)) || ((icon != null) && (!icon.equals(this.mIcon)))) { 84 | this.mIcon = icon; 85 | this.notifyChanged(); 86 | } 87 | } 88 | 89 | /** 90 | * Returns the icon of this Preference. 91 | * 92 | * @return The icon. 93 | * @see #setIcon(Drawable) 94 | */ 95 | public Drawable getIcon() { 96 | return this.mIcon; 97 | } 98 | } -------------------------------------------------------------------------------- /src/com/jakewharton/breakoutwallpaper/Ball.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.breakoutwallpaper; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * Represents a single ball on the game board. 7 | * 8 | * @author Jake Wharton 9 | */ 10 | public class Ball { 11 | /** 12 | * Tag used for logging. 13 | */ 14 | private static final String TAG = "BreakoutWallpaper.Ball"; 15 | 16 | /** 17 | * Size relative to the smaller of cell width and cell height. 18 | */ 19 | /*package*/static final float SIZE_PERCENTAGE = 0.75f; 20 | 21 | /** 22 | * Ball radius in a static context. 23 | */ 24 | /*package*/static float RADIUS = 0; 25 | 26 | /** 27 | * Arbitrary ball speed. 28 | */ 29 | private static final int SPEED = 10; 30 | 31 | 32 | 33 | /** 34 | * X coordinate of on-screen location. 35 | */ 36 | private float mLocationX; 37 | 38 | /** 39 | * Y coordinate of on-screen location. 40 | */ 41 | private float mLocationY; 42 | 43 | /** 44 | * X coordinate of per-tick movement vector. 45 | */ 46 | private float mVectorX; 47 | 48 | /** 49 | * Y coordinate of per-tick movement vector. 50 | */ 51 | private float mVectorY; 52 | 53 | 54 | 55 | /** 56 | * Create a ball. 57 | */ 58 | public Ball() { 59 | if (Wallpaper.LOG_VERBOSE) { 60 | Log.v(Ball.TAG, "> Ball()"); 61 | } 62 | 63 | this.mLocationX = 0; 64 | this.mLocationY = 0; 65 | this.mVectorX = 0; 66 | this.mVectorY = 0; 67 | 68 | if (Wallpaper.LOG_VERBOSE) { 69 | Log.v(Ball.TAG, "< Ball()"); 70 | } 71 | } 72 | 73 | 74 | 75 | /** 76 | * Get X coordinate of on-screen location. 77 | * 78 | * @return Float. 79 | */ 80 | public float getLocationX() { 81 | return this.mLocationX; 82 | } 83 | 84 | /** 85 | * Get Y coordinate of on-screen location. 86 | * 87 | * @return Float. 88 | */ 89 | public float getLocationY() { 90 | return this.mLocationY; 91 | } 92 | 93 | /** 94 | * Set on-screen location. 95 | * 96 | * @param x X coordinate. 97 | * @param y Y coordinate. 98 | */ 99 | public void setLocation(final float x, final float y) { 100 | this.mLocationX = x; 101 | this.mLocationY = y; 102 | 103 | if (Wallpaper.LOG_DEBUG) { 104 | Log.d(Ball.TAG, "Location: (" + x + ", " + y + ")"); 105 | } 106 | } 107 | 108 | /** 109 | * Get X coordinate of per-tick movement vector. 110 | * 111 | * @return Float. 112 | */ 113 | public float getVectorX() { 114 | return this.mVectorY; 115 | } 116 | 117 | /** 118 | * Get Y coordinate of per-tick movement vector. 119 | * 120 | * @return Float. 121 | */ 122 | public float getVectorY() { 123 | return this.mVectorX; 124 | } 125 | 126 | /** 127 | * Set per-tick movement vector. This will be normalized and then scaled to speed. 128 | * 129 | * @param x X coordinate. 130 | * @param y Y coordinate. 131 | */ 132 | public void setVector(float x, float y) { 133 | final float length = (float)Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); 134 | x /= length; 135 | y /= length; 136 | x *= Ball.SPEED; 137 | y *= Ball.SPEED; 138 | 139 | this.mVectorX = x; 140 | this.mVectorY = y; 141 | } 142 | 143 | /** 144 | * Iterate the ball one step. 145 | */ 146 | public void tick() { 147 | this.mLocationX += this.mVectorX; 148 | this.mLocationY += this.mVectorY; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/com/jakewharton/utilities/IntegerListPreference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.jakewharton.utilities; 18 | 19 | import android.content.Context; 20 | import android.preference.ListPreference; 21 | import android.util.AttributeSet; 22 | 23 | /** 24 | * A list preference which persists its values as integers instead of strings. 25 | * Code reading the values should use 26 | * {@link android.content.SharedPreferences#getInt}. 27 | * When using XML-declared arrays for entry values, the arrays should be regular 28 | * string arrays containing valid integer values. 29 | * 30 | * @author Rodrigo Damazio 31 | */ 32 | public class IntegerListPreference extends ListPreference { 33 | /** 34 | * Create a new instance of the IntegerListPreference. 35 | * 36 | * @param context Context. 37 | */ 38 | public IntegerListPreference(final Context context) { 39 | super(context); 40 | 41 | this.verifyEntryValues(null); 42 | } 43 | 44 | /** 45 | * Create a new instnace of the IntegerListPreference. 46 | * 47 | * @param context Context. 48 | * @param attrs Attributes. 49 | */ 50 | public IntegerListPreference(final Context context, final AttributeSet attrs) { 51 | super(context, attrs); 52 | 53 | this.verifyEntryValues(null); 54 | } 55 | 56 | 57 | 58 | @Override 59 | public void setEntryValues(final CharSequence[] entryValues) { 60 | final CharSequence[] oldValues = getEntryValues(); 61 | super.setEntryValues(entryValues); 62 | this.verifyEntryValues(oldValues); 63 | } 64 | 65 | @Override 66 | public void setEntryValues(final int entryValuesResId) { 67 | final CharSequence[] oldValues = getEntryValues(); 68 | super.setEntryValues(entryValuesResId); 69 | this.verifyEntryValues(oldValues); 70 | } 71 | 72 | @Override 73 | protected String getPersistedString(final String defaultReturnValue) { 74 | //During initial load, there's no known default value 75 | int defaultIntegerValue = Integer.MIN_VALUE; 76 | if (defaultReturnValue != null) { 77 | defaultIntegerValue = Integer.parseInt(defaultReturnValue); 78 | } 79 | 80 | // When the list preference asks us to read a string, instead read an integer. 81 | int value = this.getPersistedInt(defaultIntegerValue); 82 | return Integer.toString(value); 83 | } 84 | 85 | @Override 86 | protected boolean persistString(final String value) { 87 | // When asked to save a string, instead save an integer 88 | return this.persistInt(Integer.parseInt(value)); 89 | } 90 | 91 | /** 92 | * Verify all of the values in the list. 93 | * 94 | * @param oldValues Old value. 95 | */ 96 | private void verifyEntryValues(final CharSequence[] oldValues) { 97 | final CharSequence[] entryValues = this.getEntryValues(); 98 | if (entryValues == null) { 99 | return; 100 | } 101 | 102 | for (final CharSequence entryValue : entryValues) { 103 | try { 104 | Integer.parseInt(entryValue.toString()); 105 | } catch (NumberFormatException nfe) { 106 | super.setEntryValues(oldValues); 107 | throw nfe; 108 | } 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /assets/instructions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 |

Welcome!

15 | 16 |

Game Settings

17 |

Play mode allows you to select between endless playback which continually regenerates blocks after a certain threshold has been cleared, or individual levels which must be wholly cleared before the entire board reappears.

18 |

The "User Control" setting, when enabled, allow you to touch to the top, bottom, left, and right of the screen center to influence The Man's movements. Remember, these directions are calculated from the center of the screen and NOT from The Man's current position.

19 | 20 |

Display Settings

21 |

The layout of the grid has been optimized by default for WVGA devices (800x480, 854x480, etc.). If you are using a HVGA device (480x320) the grid might not line up with your icons by default.

22 |

You can adjust the location of the grid by changing the top and bottom padding in the settings. Once in the settings, go to the "Display" category and then click the "Grid Padding" option. From here you can tweak the top and bottom padding (the offset from the edge of the screen) to your liking. Try starting with a top value of 25 and a bottom value of 50 and then adjusting to your liking.

23 |

If you are using a launcher other than the default one (ADW.Launcher, Launcher Pro, etc.) and have changed the number of icon rows and columns you will need to change that setting in the wallpaper too. They are located under "Display" as "Icon Rows" and "Icon Columns" settings.

24 | 25 |

Color Settings

26 |

Along with changing gameplay, you can customize nearly every color of the wallpaper to your liking. By default the colors have been styled to emulate a TI graphing calculator screen.

27 |

Create your own color scheme to match your phone theme or favorite colors. If you aren't ready to fully give up a picture wallpaper you can even have a static image as the background by choosing "Background Image" under "Game Board" in the "Color" category.

28 | 29 |

Ball Physics

30 |

The physics of the ball were not something which I spent a great deal of time on. The collision detection and new vector calculation is about 95% correct. The missing 5% introduces a bit of randomness and unpredictability in which direction a ball will bounce which added uniqueness to the gameplay and was intentionally left included.

31 |

If you would like to add different physics modes (such as one which follows stricter rules), feel free to fork and contribute code on the GitHub project page.

32 | 33 |

Reset

34 |

If at any time you totally screw up your settings and just want to return them to their defaults, each main category contains a reset option at the bottom of the screen. This will only reset the settings related to that category.

35 |

If you want to reset all of the wallpaper settings to default then there is a "Reset" option in the menu on the main settings screen. This will reset every setting back to its initial state.

36 | 37 |

Bugs? Force closes?

38 |

ALWAYS report bugs and force closes to jakewharton@gmail.com. There are a lot of devices and thousands of settings combinations which I probably have not had the opportunity to test and your feedback will help improve the wallpaper. Emails are infinitely more helpful than negative feedback on the Market.

39 |

Questions, comments, and suggestions are also always welcome too!

40 | 41 | -------------------------------------------------------------------------------- /src/com/jakewharton/utilities/NumberPreference.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.utilities; 2 | 3 | import com.jakewharton.breakoutwallpaper.R; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.preference.DialogPreference; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | import android.widget.SeekBar; 10 | import android.widget.TextView; 11 | 12 | /** 13 | * Preference which displays a seek bar for the selection of an integer. 14 | * 15 | * @author Jake Wharton 16 | */ 17 | public class NumberPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener { 18 | /** 19 | * The seek bar. 20 | */ 21 | private SeekBar mSeekBar; 22 | 23 | /** 24 | * Representation of the value. 25 | */ 26 | private TextView mValueText; 27 | 28 | /** 29 | * A suffix to append to the value for display. 30 | */ 31 | private String mSuffix; 32 | 33 | /** 34 | * Maximum value. 35 | */ 36 | private int mMax; 37 | 38 | /** 39 | * Minimum value. 40 | */ 41 | private int mMin; 42 | 43 | /** 44 | * The current value. 45 | */ 46 | private int mValue = 0; 47 | 48 | 49 | 50 | /** 51 | * Create a new instance of the NumberPreference. 52 | * 53 | * @param context Context. 54 | * @param attrs Attributes. 55 | */ 56 | public NumberPreference(final Context context, final AttributeSet attrs) { 57 | super(context, attrs); 58 | this.setPersistent(true); 59 | 60 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NumberPreference, 0, 0); 61 | this.mSuffix = a.getString(R.styleable.NumberPreference_suffix); 62 | this.mMin = a.getInt(R.styleable.NumberPreference_min, 0); 63 | this.mMax = a.getInt(R.styleable.NumberPreference_max, 100); 64 | 65 | this.setDialogLayoutResource(R.layout.number_preference); 66 | } 67 | 68 | 69 | 70 | @Override 71 | protected void onBindDialogView(final View view) { 72 | super.onBindDialogView(view); 73 | 74 | ((TextView)view.findViewById(R.id.dialogMessage)).setText(this.getDialogMessage()); 75 | 76 | this.mValueText = (TextView)view.findViewById(R.id.actualValue); 77 | 78 | this.mSeekBar = (SeekBar)view.findViewById(R.id.myBar); 79 | this.mSeekBar.setOnSeekBarChangeListener(this); 80 | this.mSeekBar.setMax(this.mMax - this.mMin); 81 | this.mSeekBar.setProgress(this.mValue - this.mMin); 82 | 83 | final String t = String.valueOf(this.mValue); 84 | this.mValueText.setText(this.mSuffix == null ? t : t.concat(this.mSuffix)); 85 | } 86 | 87 | @Override 88 | protected Object onGetDefaultValue(final TypedArray a, final int index) { 89 | return a.getInt(index, 0); 90 | } 91 | 92 | @Override 93 | protected void onSetInitialValue(final boolean restore, final Object defaultValue) { 94 | this.mValue = this.getPersistedInt(defaultValue == null ? 0 : (Integer)defaultValue); 95 | } 96 | 97 | @Override 98 | protected void onDialogClosed(final boolean positiveResult) { 99 | super.onDialogClosed(positiveResult); 100 | 101 | if (positiveResult) { 102 | final int value = this.mSeekBar.getProgress() + this.mMin; 103 | if (this.callChangeListener(value)) { 104 | this.saveValue(value); 105 | } 106 | } 107 | } 108 | 109 | /** 110 | * Set the value. 111 | * 112 | * @param value Value. 113 | */ 114 | public void setValue(int value) { 115 | if (value > this.mMax) { 116 | value = this.mMax; 117 | } else if (value < this.mMin) { 118 | value = this.mMin; 119 | } 120 | this.mValue = value; 121 | } 122 | 123 | /** 124 | * Set and persist the value. 125 | * 126 | * @param value Value. 127 | */ 128 | private void saveValue(final int value) { 129 | this.setValue(value); 130 | this.persistInt(value); 131 | } 132 | 133 | /** 134 | * Set the maximum possible value. 135 | * 136 | * @param max Maximum value. 137 | */ 138 | public void setMax(final int max) { 139 | this.mMax = max; 140 | if (this.mValue > this.mMax) { 141 | this.setValue(this.mMax); 142 | } 143 | } 144 | 145 | /** 146 | * Set the minimum possible value. 147 | * 148 | * @param min Minimum value. 149 | */ 150 | public void setMin(final int min) { 151 | if (min < this.mMax) { 152 | this.mMin = min; 153 | } 154 | } 155 | 156 | /** 157 | * Called when the seek bar value is changed. 158 | */ 159 | public void onProgressChanged(final SeekBar seek, final int value, final boolean fromTouch) { 160 | final String t = String.valueOf(value + this.mMin); 161 | this.mValueText.setText(this.mSuffix == null ? t : t.concat(this.mSuffix)); 162 | } 163 | 164 | /** 165 | * Called when the seek bar has started to changed. Not used. 166 | */ 167 | public void onStartTrackingTouch(final SeekBar seek) {} 168 | 169 | /** 170 | * Called when the seek bar has stopped changing. Not used. 171 | */ 172 | public void onStopTrackingTouch(final SeekBar seek) {} 173 | } -------------------------------------------------------------------------------- /src/com/jakewharton/utilities/ColorPreference.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.utilities; 2 | 3 | import com.jakewharton.breakoutwallpaper.R; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.graphics.Color; 7 | import android.preference.DialogPreference; 8 | import android.util.AttributeSet; 9 | import android.view.SurfaceView; 10 | import android.view.View; 11 | import android.widget.SeekBar; 12 | import android.widget.TextView; 13 | import android.widget.SeekBar.OnSeekBarChangeListener; 14 | 15 | /** 16 | * Preference for picking a color which is persisted as an integer. 17 | * 18 | * @author Jake Wharton 19 | */ 20 | public class ColorPreference extends DialogPreference { 21 | /** 22 | * Since we do not allow changing the alpha value, always use the maximum value of 255. 23 | */ 24 | private static final int ALPHA = 0xff; 25 | 26 | 27 | 28 | /** 29 | * Color preview at top of dialog. 30 | */ 31 | private SurfaceView mPreview; 32 | 33 | /** 34 | * Seek bar for the red color part. 35 | */ 36 | private SeekBar mR; 37 | 38 | /** 39 | * Seek bar for the green color part. 40 | */ 41 | private SeekBar mG; 42 | 43 | /** 44 | * Seek bar for the blue color part. 45 | */ 46 | private SeekBar mB; 47 | 48 | /** 49 | * Value of the red seek bar. 50 | */ 51 | private TextView mRValue; 52 | 53 | /** 54 | * Value of the green seek bar. 55 | */ 56 | private TextView mGValue; 57 | 58 | /** 59 | * Value of the blue seek bar. 60 | */ 61 | private TextView mBValue; 62 | 63 | /** 64 | * The color. 65 | */ 66 | private int mColor; 67 | 68 | /** 69 | * Temporary color storage used for callback. 70 | */ 71 | private Integer mTempColor; 72 | 73 | /** 74 | * Listener for any of the seek bar value changes. 75 | */ 76 | private final OnSeekBarChangeListener mSeekBarChangeListener = new OnSeekBarChangeListener() { 77 | public void onStopTrackingTouch(SeekBar seekBar) {} 78 | public void onStartTrackingTouch(SeekBar seekBar) {} 79 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 80 | final int red = ColorPreference.this.mR.getProgress(); 81 | final int green = ColorPreference.this.mG.getProgress(); 82 | final int blue = ColorPreference.this.mB.getProgress(); 83 | final int color = Color.argb(ColorPreference.ALPHA, red, green, blue); 84 | 85 | ColorPreference.this.mRValue.setText(Integer.toString(red)); 86 | ColorPreference.this.mGValue.setText(Integer.toString(green)); 87 | ColorPreference.this.mBValue.setText(Integer.toString(blue)); 88 | 89 | ColorPreference.this.setValue(color); 90 | } 91 | }; 92 | 93 | 94 | 95 | /** 96 | * Create a new instance of the ColorPreference. 97 | * 98 | * @param context Context. 99 | * @param attrs Attributes. 100 | */ 101 | public ColorPreference(final Context context, final AttributeSet attrs) { 102 | super(context, attrs); 103 | 104 | this.setPersistent(true); 105 | this.setDialogLayoutResource(R.layout.color_preference); 106 | } 107 | 108 | 109 | 110 | @Override 111 | protected void onBindDialogView(final View view) { 112 | super.onBindDialogView(view); 113 | 114 | this.mPreview = (SurfaceView)view.findViewById(R.id.preview); 115 | this.mPreview.setBackgroundColor(this.mColor); 116 | 117 | this.mR = (SeekBar)view.findViewById(R.id.red); 118 | this.mR.setProgress(Color.red(this.mColor)); 119 | this.mR.setOnSeekBarChangeListener(this.mSeekBarChangeListener); 120 | this.mG = (SeekBar)view.findViewById(R.id.green); 121 | this.mG.setProgress(Color.green(this.mColor)); 122 | this.mG.setOnSeekBarChangeListener(this.mSeekBarChangeListener); 123 | this.mB = (SeekBar)view.findViewById(R.id.blue); 124 | this.mB.setProgress(Color.blue(this.mColor)); 125 | this.mB.setOnSeekBarChangeListener(this.mSeekBarChangeListener); 126 | 127 | this.mRValue = (TextView)view.findViewById(R.id.red_value); 128 | this.mRValue.setText(Integer.toString(Color.red(this.mColor))); 129 | this.mGValue = (TextView)view.findViewById(R.id.green_value); 130 | this.mGValue.setText(Integer.toString(Color.green(this.mColor))); 131 | this.mBValue = (TextView)view.findViewById(R.id.blue_value); 132 | this.mBValue.setText(Integer.toString(Color.blue(this.mColor))); 133 | } 134 | 135 | @Override 136 | protected Object onGetDefaultValue(final TypedArray a, final int index) { 137 | return a.getInt(index, 0); 138 | } 139 | 140 | @Override 141 | protected void onSetInitialValue(final boolean restore, final Object defaultValue) { 142 | final int color = this.getPersistedInt(defaultValue == null ? 0 : (Integer)defaultValue); 143 | this.mColor = color; 144 | } 145 | 146 | @Override 147 | protected void onDialogClosed(final boolean positiveResult) { 148 | super.onDialogClosed(positiveResult); 149 | 150 | if (positiveResult) { 151 | this.mTempColor = this.mColor; 152 | if (this.callChangeListener(this.mTempColor)) { 153 | this.saveValue(this.mTempColor); 154 | } 155 | } 156 | } 157 | 158 | /** 159 | * Set the value of the color and update the preview. 160 | * 161 | * @param color Color value. 162 | */ 163 | public void setValue(final int color) { 164 | this.mColor = color; 165 | this.mPreview.setBackgroundColor(color); 166 | } 167 | 168 | /** 169 | * Set and persist the value of the color. 170 | * 171 | * @param color Color value. 172 | */ 173 | public void saveValue(final int color) { 174 | this.setValue(color); 175 | this.persistInt(color); 176 | } 177 | } -------------------------------------------------------------------------------- /src/com/jakewharton/breakoutwallpaper/Picker.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.breakoutwallpaper; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.content.res.Resources; 8 | import android.graphics.Canvas; 9 | import android.os.Bundle; 10 | import android.os.Handler; 11 | import android.util.Log; 12 | import android.view.View; 13 | import android.widget.Toast; 14 | 15 | /** 16 | * Activity which launches the live wallpaper picker and prompts for the 17 | * user to install our wallpaper. 18 | * 19 | * @author Jake Wharton 20 | */ 21 | public class Picker extends Activity { 22 | /** 23 | * Intent for live wallpaper picker activity. 24 | */ 25 | private static final String LIVE_WALLPAPER_CHOOSER = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER"; 26 | 27 | 28 | 29 | /** 30 | * The timed callback handler. 31 | */ 32 | private final Handler mHandler = new Handler(); 33 | 34 | 35 | 36 | @Override 37 | protected void onCreate(final Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | 40 | if (Wallpaper.PLAY_DEBUG) { 41 | //Used across the package for gameplay 42 | Wallpaper.PREFERENCES = this.getSharedPreferences(Preferences.SHARED_NAME, Context.MODE_PRIVATE); 43 | Wallpaper.CONTEXT = this; 44 | 45 | //Game it up! 46 | this.setContentView(new Bootstrapper(this)); 47 | } else { 48 | //Prompt to choose our wallpaper 49 | Toast.makeText(this, this.getResources().getString(R.string.welcome_picker_toast), Toast.LENGTH_LONG).show(); 50 | 51 | //Display wallpaper picker 52 | this.startActivity(new Intent(Picker.LIVE_WALLPAPER_CHOOSER)); 53 | 54 | //Close this helper activity 55 | this.finish(); 56 | } 57 | } 58 | 59 | 60 | 61 | 62 | private class Bootstrapper extends View implements SharedPreferences.OnSharedPreferenceChangeListener { 63 | /** 64 | * Tag used for logging. 65 | */ 66 | private static final String TAG = "BreakoutWallpaper.Bootstrapper"; 67 | 68 | 69 | 70 | /** 71 | * Instance of the game. 72 | */ 73 | private Game mGame; 74 | 75 | /** 76 | * Whether or not the wallpaper is currently visible on screen. 77 | */ 78 | private boolean mIsVisible; 79 | 80 | /** 81 | * The number of FPS the user wants us to render. 82 | */ 83 | private int mFPS; 84 | 85 | /** 86 | * A runnable which automates the frame rendering. 87 | */ 88 | private final Runnable mDrawWakka = new Runnable() { 89 | public void run() { 90 | Bootstrapper.this.newFrame(); 91 | Bootstrapper.this.invalidate(); 92 | } 93 | }; 94 | 95 | 96 | 97 | public Bootstrapper(final Context context) { 98 | super(context); 99 | 100 | if (Wallpaper.LOG_VERBOSE) { 101 | Log.v(Bootstrapper.TAG, "> Bootstrapper()"); 102 | } 103 | 104 | this.mGame = new Game(); 105 | 106 | //Load all preferences or their defaults 107 | Wallpaper.PREFERENCES.registerOnSharedPreferenceChangeListener(this); 108 | this.onSharedPreferenceChanged(Wallpaper.PREFERENCES, null); 109 | 110 | if (Wallpaper.LOG_VERBOSE) { 111 | Log.v(Bootstrapper.TAG, "< Bootstrapper()"); 112 | } 113 | } 114 | 115 | 116 | 117 | /** 118 | * Handle the changing of a preference. 119 | */ 120 | public void onSharedPreferenceChanged(final SharedPreferences preferences, final String key) { 121 | if (Wallpaper.LOG_VERBOSE) { 122 | Log.v(Bootstrapper.TAG, "> onSharedPreferenceChanged()"); 123 | } 124 | 125 | final boolean all = (key == null); 126 | final Resources resources = Wallpaper.CONTEXT.getResources(); 127 | 128 | final String fps = resources.getString(R.string.settings_display_fps_key); 129 | if (all || key.equals(fps)) { 130 | this.mFPS = preferences.getInt(fps, resources.getInteger(R.integer.display_fps_default)); 131 | 132 | if (Wallpaper.LOG_DEBUG) { 133 | Log.d(Bootstrapper.TAG, "FPS: " + this.mFPS); 134 | } 135 | } 136 | 137 | if (Wallpaper.LOG_VERBOSE) { 138 | Log.v(Bootstrapper.TAG, "< onSharedPreferenceChanged()"); 139 | } 140 | } 141 | 142 | @Override 143 | protected void onSizeChanged(final int width, final int height, final int oldWidth, final int oldHeight) { 144 | if (Wallpaper.LOG_VERBOSE) { 145 | Log.v(Bootstrapper.TAG, "> onSurfaceChanged(width = " + width + ", height = " + height + ")"); 146 | } 147 | 148 | super.onSizeChanged(width, height, oldWidth, oldHeight); 149 | 150 | //Trickle down 151 | this.mGame.performResize(width, height); 152 | 153 | //Redraw with new settings 154 | this.invalidate(); 155 | 156 | if (Wallpaper.LOG_VERBOSE) { 157 | Log.v(Bootstrapper.TAG, "< onSurfaceChanged()"); 158 | } 159 | } 160 | 161 | @Override 162 | public void onWindowFocusChanged(final boolean hasWindowFocus) { 163 | super.onWindowFocusChanged(hasWindowFocus); 164 | this.mIsVisible = hasWindowFocus; 165 | 166 | if (hasWindowFocus) { 167 | this.newFrame(); 168 | } else { 169 | Picker.this.mHandler.removeCallbacks(this.mDrawWakka); 170 | } 171 | } 172 | 173 | /** 174 | * Advance the game by one frame. 175 | */ 176 | private void newFrame() { 177 | this.mGame.tick(); 178 | 179 | if (Wallpaper.AUTO_FPS) { 180 | if (this.mIsVisible) { 181 | Picker.this.mHandler.postDelayed(this.mDrawWakka, Wallpaper.MILLISECONDS_IN_SECOND / this.mFPS); 182 | } 183 | } 184 | } 185 | 186 | @Override 187 | protected void onDraw(final Canvas canvas) { 188 | this.mGame.draw(canvas); 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/com/jakewharton/breakoutwallpaper/Wallpaper.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.breakoutwallpaper; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.content.res.Resources; 6 | import android.graphics.Canvas; 7 | import android.os.Handler; 8 | import android.service.wallpaper.WallpaperService; 9 | import android.util.Log; 10 | import android.view.MotionEvent; 11 | import android.view.SurfaceHolder; 12 | 13 | /** 14 | * Breakin' bricks, yo. 15 | * 16 | * @author Jake Wharton 17 | */ 18 | public class Wallpaper extends WallpaperService { 19 | /** 20 | * SharedPreferences instance. 21 | */ 22 | /*package*/static SharedPreferences PREFERENCES; 23 | 24 | /** 25 | * Wallpaper Context instance. 26 | */ 27 | /*package*/static Context CONTEXT; 28 | 29 | /** 30 | * Whether or not we are logging in debug mode. 31 | */ 32 | /*package*/static final boolean LOG_DEBUG = false; 33 | 34 | /** 35 | * Whether or not we are logging in verbose mode. 36 | */ 37 | /*package*/static final boolean LOG_VERBOSE = false; 38 | 39 | /** 40 | * Whether or not the wallpaper should automatically advance to the next frame. 41 | */ 42 | /*package*/static final boolean AUTO_FPS = true; 43 | 44 | /** 45 | * Whether or not to allow playback (and debugging!) in the Picker activity. 46 | */ 47 | /*package*/static final boolean PLAY_DEBUG = false; 48 | 49 | /** 50 | * Height (in DIP) of the status bar. Usually. 51 | */ 52 | private static final int STATUS_BAR_HEIGHT = 24; 53 | 54 | /** 55 | * Height (in DIP) of the app drawer on the launcher. 56 | */ 57 | private static final int APP_DRAWER_HEIGHT = 50; 58 | 59 | /** 60 | * Number of millisecond in a second. 61 | */ 62 | /*package*/static final int MILLISECONDS_IN_SECOND = 1000; 63 | 64 | /** 65 | * Maximum time between taps that will reset the game. 66 | */ 67 | /*package*/static final long RESET_THRESHOLD = 100; 68 | 69 | 70 | 71 | /** 72 | * The timed callback handler. 73 | */ 74 | private final Handler mHandler = new Handler(); 75 | 76 | 77 | 78 | @Override 79 | public Engine onCreateEngine() { 80 | Wallpaper.PREFERENCES = this.getSharedPreferences(Preferences.SHARED_NAME, Context.MODE_PRIVATE); 81 | Wallpaper.CONTEXT = this; 82 | 83 | this.performFirstRunCheckAndSetup(); 84 | 85 | return new BreakEngine(); 86 | } 87 | 88 | /** 89 | * Sets up some preferences based on screen size on the first run only. 90 | */ 91 | private void performFirstRunCheckAndSetup() { 92 | final Resources resources = this.getResources(); 93 | final int defaultVersion = resources.getInteger(R.integer.version_code_default); 94 | final int previousVersion = Wallpaper.PREFERENCES.getInt(resources.getString(R.string.version_code_key), defaultVersion); 95 | 96 | if (previousVersion == defaultVersion) { 97 | //First install 98 | final float density = this.getResources().getDisplayMetrics().density; 99 | final SharedPreferences.Editor editor = Wallpaper.PREFERENCES.edit(); 100 | 101 | //Base top and bottom padding off of known metrics 102 | final int topHeight = (int)(density * Wallpaper.STATUS_BAR_HEIGHT); 103 | final int bottomHeight = (int)(density * Wallpaper.APP_DRAWER_HEIGHT); 104 | editor.putInt(resources.getString(R.string.settings_display_padding_top_key), topHeight); 105 | editor.putInt(resources.getString(R.string.settings_display_padding_bottom_key), bottomHeight); 106 | 107 | editor.commit(); 108 | } else { 109 | //Check for any upgrade steps in upgrade order 110 | 111 | //XXX: Put upgrade scripts here... 112 | } 113 | } 114 | 115 | 116 | 117 | /** 118 | * Wallpaper engine to manage the Game instance. 119 | * 120 | * @author Jake Wharton 121 | */ 122 | private class BreakEngine extends Engine implements SharedPreferences.OnSharedPreferenceChangeListener { 123 | /** 124 | * Tag used for logging. 125 | */ 126 | private static final String TAG = "BreakoutWallpaper.BreakEngine"; 127 | 128 | 129 | 130 | /** 131 | * Instance of the game. 132 | */ 133 | private Game mGame; 134 | 135 | /** 136 | * Whether or not the wallpaper is currently visible on screen. 137 | */ 138 | private boolean mIsVisible; 139 | 140 | /** 141 | * The number of FPS the user wants us to render. 142 | */ 143 | private int mFPS; 144 | 145 | /** 146 | * Whether or not user input is taken into consideration. 147 | */ 148 | private boolean mIsControllable; 149 | 150 | /** 151 | * The absolute center of the screen horizontally. 152 | */ 153 | private float mScreenCenterX; 154 | 155 | /** 156 | * The absolute center of the screen vertically. 157 | */ 158 | private float mScreenCenterY; 159 | 160 | /** 161 | * The system milliseconds of the last user touch. 162 | */ 163 | private long mLastTouch; 164 | 165 | /** 166 | * A runnable which automates the frame rendering. 167 | */ 168 | private final Runnable mDrawWakka = new Runnable() { 169 | public void run() { 170 | BreakEngine.this.newFrame(); 171 | BreakEngine.this.draw(); 172 | } 173 | }; 174 | 175 | 176 | 177 | /** 178 | * Create instance of the engine. 179 | */ 180 | public BreakEngine() { 181 | if (Wallpaper.LOG_VERBOSE) { 182 | Log.v(BreakEngine.TAG, "> SnakeEngine()"); 183 | } 184 | 185 | this.mGame = new Game(); 186 | this.mLastTouch = 0; 187 | 188 | //Load all preferences or their defaults 189 | Wallpaper.PREFERENCES.registerOnSharedPreferenceChangeListener(this); 190 | this.onSharedPreferenceChanged(Wallpaper.PREFERENCES, null); 191 | 192 | if (Wallpaper.LOG_VERBOSE) { 193 | Log.v(BreakEngine.TAG, "< SnakeEngine()"); 194 | } 195 | } 196 | 197 | 198 | 199 | /** 200 | * Handle the changing of a preference. 201 | */ 202 | public void onSharedPreferenceChanged(final SharedPreferences preferences, final String key) { 203 | if (Wallpaper.LOG_VERBOSE) { 204 | Log.v(BreakEngine.TAG, "> onSharedPreferenceChanged()"); 205 | } 206 | 207 | final boolean all = (key == null); 208 | final Resources resources = Wallpaper.CONTEXT.getResources(); 209 | 210 | final String fps = Wallpaper.this.getString(R.string.settings_display_fps_key); 211 | if (all || key.equals(fps)) { 212 | this.mFPS = preferences.getInt(fps, resources.getInteger(R.integer.display_fps_default)); 213 | 214 | if (Wallpaper.LOG_DEBUG) { 215 | Log.d(BreakEngine.TAG, "FPS: " + this.mFPS); 216 | } 217 | } 218 | 219 | final String userControl = Wallpaper.this.getString(R.string.settings_game_usercontrol_key); 220 | if (all || key.equals(userControl)) { 221 | this.mIsControllable = preferences.getBoolean(userControl, resources.getBoolean(R.bool.game_usercontrol_default)); 222 | 223 | if (Wallpaper.LOG_DEBUG) { 224 | Log.d(BreakEngine.TAG, "Is User Controllable: " + this.mIsControllable); 225 | } 226 | } 227 | 228 | if (Wallpaper.LOG_VERBOSE) { 229 | Log.v(BreakEngine.TAG, "< onSharedPreferenceChanged()"); 230 | } 231 | } 232 | 233 | @Override 234 | public void onVisibilityChanged(final boolean visible) { 235 | this.mIsVisible = visible; 236 | if (visible) { 237 | this.draw(); 238 | 239 | if (Wallpaper.AUTO_FPS) { 240 | this.newFrame(); 241 | } 242 | } else { 243 | Wallpaper.this.mHandler.removeCallbacks(this.mDrawWakka); 244 | } 245 | } 246 | 247 | @Override 248 | public void onCreate(final SurfaceHolder surfaceHolder) { 249 | super.onCreate(surfaceHolder); 250 | 251 | if (this.mIsControllable) { 252 | //By default we don't get touch events, so enable them. 253 | this.setTouchEventsEnabled(true); 254 | } 255 | } 256 | 257 | @Override 258 | public void onDestroy() { 259 | super.onDestroy(); 260 | Wallpaper.this.mHandler.removeCallbacks(mDrawWakka); 261 | } 262 | 263 | @Override 264 | public void onTouchEvent(final MotionEvent event) { 265 | if (event.getAction() == MotionEvent.ACTION_DOWN) { 266 | final long touch = System.currentTimeMillis(); 267 | if (touch - this.mLastTouch < Wallpaper.RESET_THRESHOLD) { 268 | this.mGame.newLevel(); 269 | this.mLastTouch = 0; 270 | } else if (this.mIsControllable) { 271 | this.mLastTouch = touch; 272 | this.mGame.setTouch(event.getX(), event.getY()); 273 | } 274 | 275 | if (!Wallpaper.AUTO_FPS) { 276 | this.mGame.tick(); 277 | this.draw(); 278 | } 279 | } 280 | } 281 | 282 | @Override 283 | public void onSurfaceChanged(final SurfaceHolder holder, final int format, final int width, final int height) { 284 | if (Wallpaper.LOG_VERBOSE) { 285 | Log.v(BreakEngine.TAG, "> onSurfaceChanged(width = " + width + ", height = " + height + ")"); 286 | } 287 | 288 | super.onSurfaceChanged(holder, format, width, height); 289 | 290 | this.mScreenCenterX = width / 2.0f; 291 | this.mScreenCenterY = height / 2.0f; 292 | 293 | if (Wallpaper.LOG_DEBUG) { 294 | Log.d(BreakEngine.TAG, "Center X: " + this.mScreenCenterX); 295 | Log.d(BreakEngine.TAG, "Center Y: " + this.mScreenCenterY); 296 | } 297 | 298 | //Trickle down 299 | this.mGame.performResize(width, height); 300 | 301 | //Redraw with new settings 302 | this.draw(); 303 | 304 | if (Wallpaper.LOG_VERBOSE) { 305 | Log.v(BreakEngine.TAG, "< onSurfaceChanged()"); 306 | } 307 | } 308 | 309 | @Override 310 | public void onSurfaceDestroyed(final SurfaceHolder holder) { 311 | super.onSurfaceDestroyed(holder); 312 | this.mIsVisible = false; 313 | Wallpaper.this.mHandler.removeCallbacks(this.mDrawWakka); 314 | } 315 | 316 | /** 317 | * Advance the game by one frame. 318 | */ 319 | private void newFrame() { 320 | this.mGame.tick(); 321 | 322 | if (Wallpaper.AUTO_FPS) { 323 | if (this.mIsVisible) { 324 | Wallpaper.this.mHandler.postDelayed(this.mDrawWakka, Wallpaper.MILLISECONDS_IN_SECOND / this.mFPS); 325 | } 326 | } 327 | } 328 | 329 | /** 330 | * Draws the current state of the game to the wallpaper. 331 | */ 332 | private void draw() { 333 | final SurfaceHolder holder = this.getSurfaceHolder(); 334 | 335 | Canvas c = null; 336 | try { 337 | c = holder.lockCanvas(); 338 | if (c != null) { 339 | this.mGame.draw(c); 340 | } 341 | } finally { 342 | if (c != null) { 343 | holder.unlockCanvasAndPost(c); 344 | } 345 | } 346 | } 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Breakout Wallpaper 5 | Jake Wharton 6 | Play the brick-busting game of breakout around your icons. 7 | 1.0.2 8 | version 9 | 10 | Thanks for installing!\n\nWould you like to read the instructions? 11 | Thanks for upgrading!\n\nWould you like to read the change log? 12 | Choose \"Breakout Wallpaper\" from the list. 13 | 14 | Yes 15 | No 16 | 17 | GAME OVER 18 | 19 | 20 | Draw rectangles to indicate widget positions. Long press to delete. 21 | 22 | Are you sure you want to reset all game-related settings? 23 | All game-related settings reset. 24 | Are you sure you want to reset all display-related settings? 25 | All display-related settings reset. 26 | Are you sure you want to reset all color-related settings? 27 | All color-related settings reset. 28 | Are you sure you want to reset all settings? 29 | All settings reset. 30 | 31 | settings 32 | Settings 33 | Game 34 | game 35 | Play mode, ball count... 36 | Display 37 | display 38 | Layout, offsets, FPS... 39 | Colors and Background 40 | colors 41 | Background, bricks, ball... 42 | Reset 43 | reset 44 | 45 | Play Mode 46 | game_mode 47 | Endless or levels 48 | Endless Regen Percent 49 | game_endlessregen 50 | Percentage of blocks remaining at which to begin regen 51 | User Control 52 | game_usercontrol 53 | Allow touches on the screen to influence ball direction 54 | Ball Count 55 | game_ballcount 56 | Number of balls in the game! 57 | Reset Game Settings 58 | game_reset 59 | All game settings back to default 60 | 61 | FPS 62 | display_fps 63 | Number of times per second to advance the game 64 | Layout 65 | Icon Rows 66 | display_iconrows 67 | Number of icon rows on your home screen 68 | Icon Columns 69 | display_iconcols 70 | Number of icon columns on your home screen 71 | Icon Row Cell Spacing 72 | display_rowspacing 73 | Number of cells in an icon row 74 | Icon Column Cell Spacing 75 | display_colspacing 76 | Number of cells in an icon column 77 | Widget Locations 78 | display_widgetlocations 79 | Specify widget locations for optimal board layout 80 | Grid Padding 81 | display_padding 82 | Padding offsets on the four edges of the grid 83 | Top 84 | display_padding_top 85 | 86 | Bottom 87 | display_padding_bottom 88 | 89 | Left 90 | display_padding_left 91 | 92 | Right 93 | display_padding_right 94 | 95 | Reset Display Settings 96 | display_reset 97 | All display settings back to default 98 | 99 | Background 100 | Background Color 101 | color_game_background 102 | 103 | Background Image 104 | color_game_bgimage 105 | Background image successfully set. 106 | Image to display as opposed to a solid color 107 | Background Opacity 108 | color_game_bgopacity 109 | The level of opacity of the background image 110 | Clear Background Image 111 | color_game_bgimage_clear 112 | Background image cleared. 113 | 114 | Ball 115 | Ball Color 116 | color_ball 117 | Color of game balls 118 | Blocks 119 | Block 1 Color 120 | color_block1 121 | First color of blocks 122 | Block 2 Color 123 | color_block2 124 | Second color of blocks 125 | Block 3 Color 126 | color_block3 127 | Third color of blocks 128 | Drawing Style 129 | Block Drawing Style 130 | color_blockstyle 131 | Fill or stroke 132 | Ball Drawing Style 133 | color_ballstyle 134 | Fill or stroke 135 | Reset Color Settings 136 | color_reset 137 | All color settings back to default 138 | 139 | About 140 | about 141 | Instructions, change log, credits... 142 | about_header 143 | An Android live wallpaper which plays the brick-busting game of breakout around your icons. 144 | Contents 145 | Instructions 146 | instructions 147 | Breakout Wallpaper Instructions 148 | Simple overview of usage 149 | Change Log 150 | changelog 151 | Breakout Wallpaper Change Log 152 | Summary of differences between versions 153 | Credits 154 | credits 155 | Breakout Wallpaper Credits 156 | Additional attribution 157 | To Do / Requests 158 | todo 159 | Breakout Wallpaper To Do and Requests 160 | List of features pending implementation 161 | 162 | Around The Web 163 | GitHub Project 164 | github 165 | http://github.com/JakeWharton/BreakoutWallpaper/ 166 | We are open source! Fork and contribute code. 167 | XDA Developers 168 | xda 169 | http://forum.xda-developers.com/showthread.php?p=7979986 170 | Official thread of development information 171 | 172 | Information 173 | Version 174 | information_version 175 | Author 176 | information_author 177 | 178 | Contact 179 | E-Mail 180 | information_contact_email 181 | JakeWharton@GMail.com 182 | jakewharton@gmail.com 183 | Twitter 184 | information_contact_twitter 185 | \@JakeWharton 186 | http://twitter.com/JakeWharton 187 | Website 188 | information_contact_website 189 | http://jakewharton.com 190 | http://jakewharton.com 191 | Android Market 192 | View Other Applications 193 | information_market_view 194 | Click here to view my other applications on the Android Market 195 | market://search?q=pub:%22Jake%20Wharton%22 196 | 197 | Reset All 198 | -------------------------------------------------------------------------------- /res/xml/preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 19 | 26 | 33 | 38 | 45 | 46 | 49 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 63 | 70 | 72 | 79 | 86 | 93 | 100 | 105 | 109 | 116 | 123 | 130 | 137 | 138 | 139 | 140 | 143 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 157 | 159 | 164 | 168 | 175 | 179 | 180 | 181 | 183 | 188 | 189 | 190 | 192 | 197 | 202 | 207 | 208 | 209 | 211 | 218 | 225 | 226 | 227 | 230 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 242 | 247 | 251 | 256 | 258 | 262 | 266 | 270 | 274 | 275 | 277 | 281 | 285 | 286 | 287 | 291 | 295 | 297 | 302 | 307 | 312 | 313 | 315 | 319 | 320 | 321 | 322 | -------------------------------------------------------------------------------- /src/com/jakewharton/utilities/WidgetLocationsPreference.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.utilities; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import com.jakewharton.breakoutwallpaper.R; 6 | import android.content.Context; 7 | import android.content.res.TypedArray; 8 | import android.graphics.Canvas; 9 | import android.graphics.Color; 10 | import android.graphics.Paint; 11 | import android.graphics.Point; 12 | import android.graphics.Rect; 13 | import android.preference.DialogPreference; 14 | import android.util.AttributeSet; 15 | import android.util.Log; 16 | import android.view.GestureDetector; 17 | import android.view.MotionEvent; 18 | import android.view.View; 19 | import android.widget.LinearLayout; 20 | import android.widget.TextView; 21 | import android.widget.LinearLayout.LayoutParams; 22 | 23 | /** 24 | * Dialog preference which allows for the selection of the locations of launcher widgets. 25 | * 26 | * @author Jake Wharton 27 | */ 28 | public class WidgetLocationsPreference extends DialogPreference { 29 | /** 30 | * Tag used for logging. 31 | */ 32 | private static final String LOG = "WidgetLocationsPreference"; 33 | 34 | /** 35 | * Number of numbers stored in a rectangle (L, R, T, B). 36 | */ 37 | private static final int RECTANGLE_LENGTH = 4; 38 | 39 | /** 40 | * Offset from the sides of the dialog. 41 | */ 42 | private static final int PADDING = 10; 43 | 44 | 45 | 46 | /** 47 | * Widget location view. 48 | */ 49 | private WidgetLocatorView mView; 50 | 51 | /** 52 | * The string representation of the locations. 53 | */ 54 | private String mValue; 55 | 56 | /** 57 | * The string representation of the locations used for the save callback. 58 | */ 59 | private String mTempValue; 60 | 61 | /** 62 | * Number of icon rows on the launcher. 63 | */ 64 | private int mIconRows; 65 | 66 | /** 67 | * Number of icon columns on the launcher. 68 | */ 69 | private int mIconCols; 70 | 71 | 72 | 73 | /** 74 | * Create a new instance of the WidgetLocationsPreference. 75 | * 76 | * @param context Context. 77 | * @param attrs Attributes. 78 | */ 79 | public WidgetLocationsPreference(final Context context, final AttributeSet attrs) { 80 | super(context, attrs); 81 | 82 | this.setPersistent(true); 83 | } 84 | 85 | 86 | 87 | @Override 88 | protected View onCreateDialogView() { 89 | final Context context = this.getContext(); 90 | final LinearLayout layout = new LinearLayout(context); 91 | layout.setOrientation(LinearLayout.VERTICAL); 92 | layout.setPadding(WidgetLocationsPreference.PADDING, WidgetLocationsPreference.PADDING, WidgetLocationsPreference.PADDING, WidgetLocationsPreference.PADDING); 93 | 94 | final TextView text = new TextView(context); 95 | text.setText(R.string.widgetlocations_howto); 96 | layout.addView(text, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); 97 | 98 | this.mView = new WidgetLocatorView(context, this.mIconRows, this.mIconCols, this.mValue); 99 | layout.addView(this.mView, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); 100 | 101 | return layout; 102 | } 103 | 104 | /** 105 | * Update the number of icon rows and columns on the launcher. 106 | * 107 | * @param iconRows Number of rows. 108 | * @param iconCols Number of columns. 109 | */ 110 | public void setIconCounts(final int iconRows, final int iconCols) { 111 | this.mIconRows = iconRows; 112 | this.mIconCols = iconCols; 113 | } 114 | 115 | @Override 116 | protected Object onGetDefaultValue(final TypedArray a, final int index) { 117 | return a.getString(index); 118 | } 119 | 120 | @Override 121 | protected void onSetInitialValue(final boolean restore, final Object defaultValue) { 122 | this.mValue = this.getPersistedString(defaultValue == null ? "" : (String)defaultValue); 123 | } 124 | 125 | @Override 126 | protected void onDialogClosed(final boolean positiveResult) { 127 | super.onDialogClosed(positiveResult); 128 | 129 | if (positiveResult) { 130 | this.mTempValue = this.mValue; 131 | if (this.callChangeListener(this.mTempValue)) { 132 | this.saveValue(this.mTempValue); 133 | } 134 | } 135 | } 136 | 137 | /** 138 | * Set and persist the string representation of the widget locations. 139 | * @param value 140 | */ 141 | private void saveValue(final String value) { 142 | this.setValue(value); 143 | this.persistString(value); 144 | } 145 | 146 | /** 147 | * Set the string representation of the widget locations. 148 | * @param value 149 | */ 150 | private void setValue(final String value) { 151 | this.mValue = value; 152 | } 153 | 154 | 155 | 156 | /** 157 | * Convert a persisted string value to the actual widget locations. 158 | * 159 | * @param string Persisted string. 160 | * @return List of Rects where the widgets are located. 161 | */ 162 | public static List convertStringToWidgetList(final String string) { 163 | final List list = new LinkedList(); 164 | 165 | if ((string.length() % WidgetLocationsPreference.RECTANGLE_LENGTH) != 0) { 166 | throw new IllegalArgumentException("String length must be a multiple of four."); 167 | } 168 | 169 | int i = 0; 170 | while (i < string.length()) { 171 | try { 172 | final Rect r = new Rect(); 173 | r.left = Integer.parseInt(String.valueOf(string.charAt(i))); 174 | r.top = Integer.parseInt(String.valueOf(string.charAt(i+1))); 175 | r.right = Integer.parseInt(String.valueOf(string.charAt(i+2))); 176 | r.bottom = Integer.parseInt(String.valueOf(string.charAt(i+3))); 177 | list.add(r); 178 | } catch (NumberFormatException e) { 179 | Log.w(WidgetLocationsPreference.LOG, "Invalid rectangle: " + string.substring(i, WidgetLocationsPreference.RECTANGLE_LENGTH)); 180 | } finally { 181 | i += WidgetLocationsPreference.RECTANGLE_LENGTH; 182 | } 183 | } 184 | 185 | return list; 186 | } 187 | 188 | 189 | 190 | /** 191 | * View which allows for the selecting of widget locations 192 | * 193 | * @author Jake Wharton 194 | */ 195 | private class WidgetLocatorView extends View { 196 | /** 197 | * Offset from the sides of the view. 198 | */ 199 | private static final float OFFSET = 5; 200 | 201 | 202 | 203 | /** 204 | * Location at which the current widget location begins. 205 | */ 206 | private Point mTouchStart; 207 | 208 | /** 209 | * Location at which the current widget location ends. 210 | */ 211 | private Point mTouchEnd; 212 | 213 | /** 214 | * Number of icon rows to display. 215 | */ 216 | private final int mRows; 217 | 218 | /** 219 | * Number of icon columns to display. 220 | */ 221 | private final int mCols; 222 | 223 | /** 224 | * The width of a single icon. 225 | */ 226 | private float mIconWidth; 227 | 228 | /** 229 | * The height of a single icon. 230 | */ 231 | private float mIconHeight; 232 | 233 | /** 234 | * The width of the virtual screen on the view. 235 | */ 236 | private float mWidth; 237 | 238 | /** 239 | * The width of the virtual screen on the view. 240 | */ 241 | private float mHeight; 242 | 243 | /** 244 | * Paint used to draw the icon divider lines. 245 | */ 246 | private final Paint mLine; 247 | 248 | /** 249 | * Paint used to draw the current widget. 250 | */ 251 | private final Paint mDrawing; 252 | 253 | /** 254 | * Paint used to draw the existing widgets. 255 | */ 256 | private final Paint mWidget; 257 | 258 | /** 259 | * List of current existing widget locations. 260 | */ 261 | private final List mWidgets; 262 | 263 | /** 264 | * Detect long-presses on the view. 265 | */ 266 | private final GestureDetector gestureDetector = new GestureDetector(new GestureDetector.OnGestureListener() { 267 | public boolean onSingleTapUp(MotionEvent e) { return false; } 268 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } 269 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; } 270 | public boolean onDown(MotionEvent e) { return false; } 271 | public void onShowPress(MotionEvent e) {} 272 | public void onLongPress(MotionEvent e) { 273 | WidgetLocatorView.this.delete(); 274 | } 275 | }); 276 | 277 | 278 | 279 | /** 280 | * Create a new instance of the WidgetLocatorView. 281 | * 282 | * @param context Context. 283 | * @param rows Number of icon rows. 284 | * @param cols Number of icon columns. 285 | * @param value Persisted value of widget location representation. 286 | */ 287 | public WidgetLocatorView(final Context context, final int rows, final int cols, final String value) { 288 | super(context); 289 | 290 | this.mRows = rows; 291 | this.mCols = cols; 292 | 293 | this.mLine = new Paint(Paint.ANTI_ALIAS_FLAG); 294 | this.mLine.setColor(Color.GRAY); 295 | this.mLine.setStrokeWidth(2); 296 | 297 | this.mDrawing = new Paint(Paint.ANTI_ALIAS_FLAG); 298 | this.mDrawing.setColor(Color.RED); 299 | this.mDrawing.setStyle(Paint.Style.STROKE); 300 | 301 | this.mWidget = new Paint(Paint.ANTI_ALIAS_FLAG); 302 | this.mWidget.setColor(Color.GREEN); 303 | this.mWidget.setStyle(Paint.Style.STROKE); 304 | 305 | this.mWidgets = WidgetLocationsPreference.convertStringToWidgetList(value); 306 | } 307 | 308 | 309 | 310 | @Override 311 | protected void onDraw(final Canvas c) { 312 | c.save(); 313 | c.translate(WidgetLocatorView.OFFSET, WidgetLocatorView.OFFSET); 314 | 315 | //Draw lines 316 | for (int row = 0; row <= this.mRows; row++) { 317 | final float rowPosition = row * this.mIconHeight; 318 | c.drawLine(0, rowPosition, this.mWidth, rowPosition, this.mLine); 319 | } 320 | for (int col = 0; col <= this.mCols; col++) { 321 | final float colPosition = col * this.mIconWidth; 322 | c.drawLine(colPosition, 0, colPosition, this.mHeight, this.mLine); 323 | } 324 | 325 | final float iconWidthOverTwo = this.mIconWidth / 2.0f; 326 | final float iconHeightOverTwo = this.mIconHeight / 2.0f; 327 | final float offset = ((this.mIconHeight < this.mIconWidth) ? this.mIconHeight : this.mIconWidth) / 4.0f; 328 | 329 | //Saved widgets 330 | for (final Rect widget : this.mWidgets) { 331 | final float left = (widget.left * this.mIconWidth) + iconWidthOverTwo - offset; 332 | final float right = (widget.right * this.mIconWidth) + iconWidthOverTwo + offset; 333 | final float top = (widget.top * this.mIconHeight) + iconHeightOverTwo - offset; 334 | final float bottom = (widget.bottom * this.mIconHeight) + iconHeightOverTwo + offset; 335 | 336 | c.drawRect(left, top, right, bottom, this.mWidget); 337 | c.drawLine(left, top, right, bottom, this.mWidget); 338 | c.drawLine(left, bottom, right, top, this.mWidget); 339 | } 340 | 341 | //Currently drawing widget 342 | if (this.mTouchStart != null) { 343 | final Rect pointRect = this.toRectangle(); 344 | final float left = (pointRect.left * this.mIconWidth) + iconWidthOverTwo - offset; 345 | final float right = (pointRect.right * this.mIconWidth) + iconWidthOverTwo + offset; 346 | final float top = (pointRect.top * this.mIconHeight) + iconHeightOverTwo - offset; 347 | final float bottom = (pointRect.bottom * this.mIconHeight) + iconHeightOverTwo + offset; 348 | 349 | c.drawRect(left, top, right, bottom, this.mDrawing); 350 | } 351 | 352 | c.restore(); 353 | } 354 | 355 | @Override 356 | protected void onSizeChanged(final int width, final int height, final int oldWidth, final int oldHeight) { 357 | super.onSizeChanged(width, height, oldWidth, oldHeight); 358 | 359 | this.mWidth = width - (2 * WidgetLocatorView.OFFSET); 360 | this.mHeight = height - (2 * WidgetLocatorView.OFFSET); 361 | this.mIconWidth = this.mWidth / (1.0f * this.mCols); 362 | this.mIconHeight = this.mHeight / (1.0f * this.mRows); 363 | } 364 | 365 | @Override 366 | public boolean onTouchEvent(final MotionEvent event) { 367 | if (this.gestureDetector.onTouchEvent(event)) { 368 | return true; 369 | } 370 | 371 | switch (event.getAction()) { 372 | case MotionEvent.ACTION_DOWN: 373 | this.mTouchStart = this.mTouchEnd = this.getPoint(event.getX(), event.getY()); 374 | 375 | this.invalidate(); 376 | return true; 377 | 378 | case MotionEvent.ACTION_MOVE: 379 | this.mTouchEnd = this.getPoint(event.getX(), event.getY()); 380 | 381 | this.invalidate(); 382 | return true; 383 | 384 | case MotionEvent.ACTION_UP: 385 | case MotionEvent.ACTION_CANCEL: 386 | this.mTouchEnd = this.getPoint(event.getX(), event.getY()); 387 | this.add(); 388 | 389 | this.mTouchStart = null; 390 | this.mTouchEnd = null; 391 | 392 | this.invalidate(); 393 | return true; 394 | 395 | default: 396 | return super.onTouchEvent(event); 397 | } 398 | } 399 | 400 | /** 401 | * Add a new widget using the two touch point locations as corners. 402 | */ 403 | private void add() { 404 | final Rect newWidget = this.toRectangle(); 405 | final Rect insetWidget = new Rect(newWidget); 406 | 407 | //This is so that intersect returns true if they are actually adjacent 408 | insetWidget.inset(-1, -1); 409 | 410 | for (final Rect widget : this.mWidgets) { 411 | if (Rect.intersects(widget, insetWidget)) { 412 | return; 413 | } 414 | } 415 | 416 | if ((newWidget.height() == 0) && (newWidget.width() == 0)) { 417 | return; 418 | } 419 | 420 | this.mWidgets.add(newWidget); 421 | this.save(); 422 | } 423 | 424 | /** 425 | * Delete a widget at the long-pressed poisiton (if it exists). 426 | */ 427 | private void delete() { 428 | for (final Rect widget : this.mWidgets) { 429 | if ((this.mTouchEnd.x >= widget.left) && (this.mTouchEnd.x <= widget.right) && (this.mTouchEnd.y >= widget.top) && (this.mTouchEnd.y <= widget.bottom)) { 430 | this.mWidgets.remove(widget); 431 | break; 432 | } 433 | } 434 | this.save(); 435 | this.invalidate(); 436 | } 437 | 438 | /** 439 | * Save the value to the parent instance. 440 | */ 441 | private void save() { 442 | final StringBuilder builder = new StringBuilder(); 443 | for (final Rect widget : this.mWidgets) { 444 | builder.append(Integer.toString(widget.left)); 445 | builder.append(Integer.toString(widget.top)); 446 | builder.append(Integer.toString(widget.right)); 447 | builder.append(Integer.toString(widget.bottom)); 448 | } 449 | WidgetLocationsPreference.this.setValue(builder.toString()); 450 | } 451 | 452 | /** 453 | * Get the icon location Point from the current pixel coordinates. 454 | * 455 | * @param x X coordinate. 456 | * @param y Y coordinate. 457 | * @return Icon location Point. 458 | */ 459 | private Point getPoint(float x, float y) { 460 | x -= WidgetLocatorView.OFFSET; 461 | y -= WidgetLocatorView.OFFSET; 462 | int newX = (int)(x / this.mIconWidth); 463 | int newY = (int)(y / this.mIconHeight); 464 | 465 | if (newX < 0) { 466 | newX = 0; 467 | } else if (newX >= this.mCols) { 468 | newX = this.mCols - 1; 469 | } 470 | if (newY < 0) { 471 | newY = 0; 472 | } else if (newY >= this.mRows) { 473 | newY = this.mRows - 1; 474 | } 475 | 476 | return new Point(newX, newY); 477 | } 478 | 479 | /** 480 | * Convert the two touch Points to a Rect. 481 | * 482 | * @return Rect with corners at the two touch points. 483 | */ 484 | private Rect toRectangle() { 485 | final boolean isStartXSmaller = (this.mTouchStart.x < this.mTouchEnd.x); 486 | final boolean isStartYSmaller = (this.mTouchStart.y < this.mTouchEnd.y); 487 | 488 | final Rect r = new Rect(); 489 | r.left = isStartXSmaller ? this.mTouchStart.x : this.mTouchEnd.x; 490 | r.right = isStartXSmaller ? this.mTouchEnd.x : this.mTouchStart.x; 491 | r.top = isStartYSmaller ? this.mTouchStart.y : this.mTouchEnd.y; 492 | r.bottom = isStartYSmaller ? this.mTouchEnd.y : this.mTouchStart.y; 493 | 494 | return r; 495 | } 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /src/com/jakewharton/breakoutwallpaper/Preferences.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.breakoutwallpaper; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.DialogInterface; 6 | import android.content.Intent; 7 | import android.content.SharedPreferences; 8 | import android.content.DialogInterface.OnClickListener; 9 | import android.content.res.Resources; 10 | import android.net.Uri; 11 | import android.os.Bundle; 12 | import android.preference.Preference; 13 | import android.preference.PreferenceActivity; 14 | import android.preference.PreferenceManager; 15 | import android.preference.Preference.OnPreferenceClickListener; 16 | import android.provider.MediaStore; 17 | import android.view.Menu; 18 | import android.view.MenuItem; 19 | import android.widget.Toast; 20 | import com.jakewharton.utilities.WidgetLocationsPreference; 21 | 22 | /** 23 | * Settings activity for SnakeWallpaper 24 | * 25 | * @author Jake Wharton 26 | */ 27 | public class Preferences extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener { 28 | /** 29 | * SharedPreference name. 30 | */ 31 | /*package*/static final String SHARED_NAME = "BreakoutWallpaper"; 32 | 33 | /** 34 | * Filename of the change log. 35 | */ 36 | private static final String FILENAME_CHANGE_LOG = "changelog.html"; 37 | 38 | /** 39 | * Filename of the credits. 40 | */ 41 | private static final String FILENAME_CREDITS = "credits.html"; 42 | 43 | /** 44 | * Filename of the instructions. 45 | */ 46 | private static final String FILENAME_INSTRUCTIONS = "instructions.html"; 47 | 48 | /** 49 | * Filename of the to do list. 50 | */ 51 | private static final String FILENAME_TODO = "todo.html"; 52 | 53 | /** 54 | * Select background activity callback ID. 55 | */ 56 | private static final int SELECT_BACKGROUND = 1; 57 | 58 | 59 | 60 | @Override 61 | protected void onCreate(final Bundle icicle) { 62 | super.onCreate(icicle); 63 | 64 | final PreferenceManager manager = this.getPreferenceManager(); 65 | manager.setSharedPreferencesName(Preferences.SHARED_NAME); 66 | this.addPreferencesFromResource(R.xml.preferences); 67 | 68 | final SharedPreferences preferences = manager.getSharedPreferences(); 69 | final Resources resources = this.getResources(); 70 | 71 | //reset game 72 | this.findPreference(resources.getString(R.string.settings_game_reset_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 73 | public boolean onPreferenceClick(final Preference preference) { 74 | (new AlertDialog.Builder(Preferences.this)) 75 | .setMessage(resources.getString(R.string.reset_game)) 76 | .setCancelable(false) 77 | .setPositiveButton(resources.getString(R.string.yes), new DialogInterface.OnClickListener() { 78 | public void onClick(final DialogInterface dialog, final int which) { 79 | Preferences.this.loadGameDefaults(); 80 | 81 | Toast.makeText(Preferences.this, resources.getString(R.string.reset_game_toast), Toast.LENGTH_LONG).show(); 82 | } 83 | }) 84 | .setNegativeButton(resources.getString(R.string.no), null) 85 | .show(); 86 | return true; 87 | } 88 | }); 89 | 90 | //reset display 91 | this.findPreference(resources.getString(R.string.settings_display_reset_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 92 | public boolean onPreferenceClick(final Preference preference) { 93 | (new AlertDialog.Builder(Preferences.this)) 94 | .setMessage(resources.getString(R.string.reset_display)) 95 | .setCancelable(false) 96 | .setPositiveButton(resources.getString(R.string.yes), new DialogInterface.OnClickListener() { 97 | public void onClick(final DialogInterface dialog, final int which) { 98 | Preferences.this.loadDisplayDefaults(); 99 | 100 | Toast.makeText(Preferences.this, resources.getString(R.string.reset_display_toast), Toast.LENGTH_LONG).show(); 101 | } 102 | }) 103 | .setNegativeButton(resources.getString(R.string.no), null) 104 | .show(); 105 | return true; 106 | } 107 | }); 108 | 109 | //reset colors 110 | this.findPreference(resources.getString(R.string.settings_color_reset_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 111 | public boolean onPreferenceClick(final Preference preference) { 112 | (new AlertDialog.Builder(Preferences.this)) 113 | .setMessage(resources.getString(R.string.reset_color)) 114 | .setCancelable(false) 115 | .setPositiveButton(resources.getString(R.string.yes), new DialogInterface.OnClickListener() { 116 | public void onClick(final DialogInterface dialog, final int which) { 117 | Preferences.this.loadColorDefaults(); 118 | 119 | Toast.makeText(Preferences.this, resources.getString(R.string.reset_color_toast), Toast.LENGTH_LONG).show(); 120 | } 121 | }) 122 | .setNegativeButton(resources.getString(R.string.no), null) 123 | .show(); 124 | return true; 125 | } 126 | }); 127 | 128 | //info email 129 | this.findPreference(resources.getString(R.string.information_contact_email_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 130 | public boolean onPreferenceClick(final Preference preference) { 131 | Preferences.this.infoEmail(); 132 | return true; 133 | } 134 | }); 135 | 136 | //info twitter 137 | this.findPreference(resources.getString(R.string.information_contact_twitter_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 138 | public boolean onPreferenceClick(final Preference preference) { 139 | Preferences.this.infoTwitter(); 140 | return true; 141 | } 142 | }); 143 | 144 | //info web 145 | this.findPreference(resources.getString(R.string.information_contact_website_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 146 | public boolean onPreferenceClick(final Preference preference) { 147 | Preferences.this.infoWeb(); 148 | return true; 149 | } 150 | }); 151 | 152 | //info market 153 | this.findPreference(resources.getString(R.string.information_market_view_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 154 | public boolean onPreferenceClick(final Preference preference) { 155 | Preferences.this.infoMarket(); 156 | return true; 157 | } 158 | }); 159 | 160 | //instructions 161 | this.findPreference(resources.getString(R.string.instructions_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 162 | public boolean onPreferenceClick(final Preference preference) { 163 | Preferences.this.viewInstructions(); 164 | return true; 165 | } 166 | }); 167 | 168 | //change log 169 | this.findPreference(resources.getString(R.string.changelog_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 170 | public boolean onPreferenceClick(final Preference preference) { 171 | Preferences.this.viewChangelog(); 172 | return true; 173 | } 174 | }); 175 | 176 | //credits 177 | this.findPreference(resources.getString(R.string.credits_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 178 | public boolean onPreferenceClick(final Preference preference) { 179 | Preferences.this.viewCredits(); 180 | return true; 181 | } 182 | }); 183 | 184 | //todo 185 | this.findPreference(resources.getString(R.string.todo_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 186 | public boolean onPreferenceClick(final Preference preference) { 187 | Preferences.this.viewTodo(); 188 | return true; 189 | } 190 | }); 191 | 192 | //github 193 | this.findPreference(resources.getString(R.string.github_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 194 | public boolean onPreferenceClick(Preference preference) { 195 | Preferences.this.viewGitHub(); 196 | return true; 197 | } 198 | }); 199 | 200 | //xda 201 | this.findPreference(resources.getString(R.string.xda_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 202 | public boolean onPreferenceClick(Preference preference) { 203 | Preferences.this.viewXda(); 204 | return true; 205 | } 206 | }); 207 | 208 | //background image 209 | this.findPreference(resources.getString(R.string.settings_color_bgimage_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 210 | public boolean onPreferenceClick(final Preference preference) { 211 | Preferences.this.startActivityForResult(new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI), Preferences.SELECT_BACKGROUND); 212 | return true; 213 | } 214 | }); 215 | 216 | //clear background image 217 | this.findPreference(resources.getString(R.string.settings_color_bgimageclear_key)).setOnPreferenceClickListener(new OnPreferenceClickListener() { 218 | public boolean onPreferenceClick(final Preference preference) { 219 | Preferences.this.getPreferenceManager().getSharedPreferences().edit().putString(resources.getString(R.string.settings_color_bgimage_key), null).commit(); 220 | Toast.makeText(Preferences.this, R.string.settings_color_bgimageclear_toast, Toast.LENGTH_SHORT).show(); 221 | return true; 222 | } 223 | }); 224 | 225 | //Register as a preference change listener 226 | Wallpaper.PREFERENCES.registerOnSharedPreferenceChangeListener(this); 227 | this.onSharedPreferenceChanged(Wallpaper.PREFERENCES, null); 228 | 229 | //Check previously installed version 230 | final int thisVersion = resources.getInteger(R.integer.version_code); 231 | final int defaultVersion = resources.getInteger(R.integer.version_code_default); 232 | final int previousVersion = preferences.getInt(resources.getString(R.string.version_code_key), defaultVersion); 233 | if (previousVersion == defaultVersion) { 234 | //First install 235 | 236 | //Store this version 237 | this.getPreferenceManager().getSharedPreferences().edit().putInt(resources.getString(R.string.version_code_key), thisVersion).commit(); 238 | //Show hello 239 | (new AlertDialog.Builder(this)) 240 | .setTitle(resources.getString(R.string.title)) 241 | .setMessage(resources.getString(R.string.welcome_firstrun)) 242 | .setCancelable(true) 243 | .setPositiveButton(resources.getString(R.string.yes), new OnClickListener() { 244 | public void onClick(final DialogInterface dialog, final int which) { 245 | Preferences.this.viewInstructions(); 246 | } 247 | }) 248 | .setNegativeButton(resources.getString(R.string.no), null) 249 | .show(); 250 | } else if (previousVersion < thisVersion) { 251 | //First run after upgrade 252 | 253 | //Store this version 254 | this.getPreferenceManager().getSharedPreferences().edit().putInt(resources.getString(R.string.version_code_key), thisVersion).commit(); 255 | //Show hello 256 | (new AlertDialog.Builder(this)) 257 | .setTitle(resources.getString(R.string.title)) 258 | .setMessage(resources.getString(R.string.welcome_upgrade)) 259 | .setCancelable(true) 260 | .setPositiveButton(resources.getString(R.string.yes), new OnClickListener() { 261 | public void onClick(final DialogInterface dialog, final int which) { 262 | Preferences.this.viewChangelog(); 263 | } 264 | }) 265 | .setNegativeButton(resources.getString(R.string.no), null) 266 | .show(); 267 | } 268 | } 269 | 270 | /** 271 | * Open change log. 272 | */ 273 | private void viewChangelog() { 274 | final Intent intent = new Intent(this, About.class); 275 | intent.putExtra(About.EXTRA_FILENAME, Preferences.FILENAME_CHANGE_LOG); 276 | intent.putExtra(About.EXTRA_TITLE, this.getResources().getString(R.string.changelog_title)); 277 | this.startActivity(intent); 278 | } 279 | 280 | /** 281 | * Open instructions. 282 | */ 283 | private void viewInstructions() { 284 | final Intent intent = new Intent(this, About.class); 285 | intent.putExtra(About.EXTRA_FILENAME, Preferences.FILENAME_INSTRUCTIONS); 286 | intent.putExtra(About.EXTRA_TITLE, this.getResources().getString(R.string.instructions_title)); 287 | this.startActivity(intent); 288 | } 289 | 290 | /** 291 | * Open credits 292 | */ 293 | private void viewCredits() { 294 | final Intent intent = new Intent(this, About.class); 295 | intent.putExtra(About.EXTRA_FILENAME, Preferences.FILENAME_CREDITS); 296 | intent.putExtra(About.EXTRA_TITLE, this.getResources().getString(R.string.credits_title)); 297 | this.startActivity(intent); 298 | } 299 | 300 | /** 301 | * Open todo 302 | */ 303 | private void viewTodo() { 304 | final Intent intent = new Intent(this, About.class); 305 | intent.putExtra(About.EXTRA_FILENAME, Preferences.FILENAME_TODO); 306 | intent.putExtra(About.EXTRA_TITLE, this.getResources().getString(R.string.todo_title)); 307 | this.startActivity(intent); 308 | } 309 | 310 | /** 311 | * Open GitHub 312 | */ 313 | private void viewGitHub() { 314 | final Intent intent = new Intent(Intent.ACTION_VIEW); 315 | intent.setData(Uri.parse(this.getResources().getString(R.string.github_href))); 316 | 317 | this.startActivity(intent); 318 | } 319 | 320 | /** 321 | * Open XDA 322 | */ 323 | private void viewXda() { 324 | final Intent intent = new Intent(Intent.ACTION_VIEW); 325 | intent.setData(Uri.parse(this.getResources().getString(R.string.xda_href))); 326 | 327 | this.startActivity(intent); 328 | } 329 | 330 | public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { 331 | final boolean all = (key == null); 332 | final Resources resources = this.getResources(); 333 | 334 | //Only enabled endless regen percentage when on endless game mode 335 | final String gameMode = resources.getString(R.string.settings_game_mode_key); 336 | if (all || key.equals(gameMode)) { 337 | final boolean enabled = (preferences.getInt(gameMode, resources.getInteger(R.integer.game_mode_default)) == Game.MODE_ENDLESS); 338 | this.findPreference(resources.getString(R.string.settings_game_endlessregen_key)).setEnabled(enabled); 339 | } 340 | 341 | //Only enable clear bg image when a bg image is set 342 | final String bgimage = resources.getString(R.string.settings_color_bgimage_key); 343 | if (all || key.equals(bgimage)) { 344 | final boolean enabled = preferences.getString(bgimage, null) != null; 345 | this.findPreference(resources.getString(R.string.settings_color_bgimageclear_key)).setEnabled(enabled); 346 | this.findPreference(resources.getString(R.string.settings_color_bgopacity_key)).setEnabled(enabled); 347 | } 348 | 349 | //If the icon rows or cols are explicitly changed then clear the widget locations 350 | final String iconRows = resources.getString(R.string.settings_display_iconrows_key); 351 | final String iconCols = resources.getString(R.string.settings_display_iconcols_key); 352 | if (all || key.equals(iconRows) || key.equals(iconCols)) { 353 | final int rows = preferences.getInt(iconRows, resources.getInteger(R.integer.display_iconrows_default)); 354 | final int cols = preferences.getInt(iconCols, resources.getInteger(R.integer.display_iconcols_default)); 355 | final String widgetLocations = resources.getString(R.string.settings_display_widgetlocations_key); 356 | 357 | if (!all) { 358 | //Clear any layouts 359 | preferences.edit().putString(widgetLocations, resources.getString(R.string.display_widgetlocations_default)).commit(); 360 | } 361 | 362 | //Update with counts 363 | ((WidgetLocationsPreference)this.findPreference(widgetLocations)).setIconCounts(rows, cols); 364 | } 365 | } 366 | 367 | @Override 368 | public boolean onCreateOptionsMenu(Menu menu) { 369 | this.getMenuInflater().inflate(R.menu.preferences, menu); 370 | return true; 371 | } 372 | 373 | @Override 374 | public boolean onOptionsItemSelected(MenuItem item) { 375 | final Resources resources = this.getResources(); 376 | 377 | switch (item.getItemId()) { 378 | case R.id.menu_reset: 379 | (new AlertDialog.Builder(this)) 380 | .setMessage(resources.getString(R.string.reset_all)) 381 | .setCancelable(false) 382 | .setPositiveButton(resources.getString(R.string.yes), new DialogInterface.OnClickListener() { 383 | public void onClick(DialogInterface dialog, int which) { 384 | Preferences.this.loadGameDefaults(); 385 | Preferences.this.loadDisplayDefaults(); 386 | Preferences.this.loadColorDefaults(); 387 | 388 | Toast.makeText(Preferences.this, resources.getString(R.string.reset_all_toast), Toast.LENGTH_LONG).show(); 389 | } 390 | }) 391 | .setNegativeButton(resources.getString(R.string.no), null) 392 | .show(); 393 | return true; 394 | 395 | default: 396 | return super.onOptionsItemSelected(item); 397 | } 398 | } 399 | 400 | @Override 401 | protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { 402 | if (resultCode == Activity.RESULT_OK) { 403 | final Resources resources = this.getResources(); 404 | 405 | switch (requestCode) { 406 | case Preferences.SELECT_BACKGROUND: 407 | //Store the string value of the background image 408 | this.getPreferenceManager().getSharedPreferences().edit().putString(resources.getString(R.string.settings_color_bgimage_key), data.getDataString()).commit(); 409 | Toast.makeText(this, R.string.settings_color_bgimage_toast, Toast.LENGTH_SHORT).show(); 410 | break; 411 | 412 | default: 413 | super.onActivityResult(requestCode, resultCode, data); 414 | } 415 | } 416 | } 417 | 418 | /** 419 | * Launch an intent to send an email. 420 | */ 421 | private void infoEmail() { 422 | final Resources resources = this.getResources(); 423 | final Intent intent = new Intent(Intent.ACTION_SEND); 424 | intent.setType("plain/text"); 425 | intent.putExtra(Intent.EXTRA_EMAIL, new String[] { resources.getString(R.string.information_contact_email_data) }); 426 | intent.putExtra(Intent.EXTRA_SUBJECT, resources.getString(R.string.title)); 427 | 428 | this.startActivity(intent); 429 | } 430 | 431 | /** 432 | * Launch an intent to view twitter page. 433 | */ 434 | private void infoTwitter() { 435 | final Resources resources = this.getResources(); 436 | final Intent intent = new Intent(Intent.ACTION_VIEW); 437 | intent.setData(Uri.parse(resources.getString(R.string.information_contact_twitter_data))); 438 | 439 | this.startActivity(intent); 440 | } 441 | 442 | /** 443 | * Launch an intent to view website. 444 | */ 445 | private void infoWeb() { 446 | final Resources resources = this.getResources(); 447 | final Intent intent = new Intent(Intent.ACTION_VIEW); 448 | intent.setData(Uri.parse(resources.getString(R.string.information_contact_website_data))); 449 | 450 | this.startActivity(intent); 451 | } 452 | 453 | /** 454 | * Launch an intent to view other market applications. 455 | */ 456 | private void infoMarket() 457 | { 458 | final Resources resources = this.getResources(); 459 | final Intent intent = new Intent(Intent.ACTION_VIEW); 460 | intent.setData(Uri.parse(resources.getString(R.string.information_market_view_data))); 461 | 462 | this.startActivity(intent); 463 | } 464 | 465 | /** 466 | * Reset display preferences to their defaults. 467 | */ 468 | private void loadGameDefaults() { 469 | final Resources resources = this.getResources(); 470 | final SharedPreferences.Editor editor = Preferences.this.getPreferenceManager().getSharedPreferences().edit(); 471 | 472 | //game mode 473 | editor.remove(resources.getString(R.string.settings_game_mode_key)); 474 | //endless regen 475 | editor.remove(resources.getString(R.string.settings_game_endlessregen_key)); 476 | //user controllable 477 | editor.remove(resources.getString(R.string.settings_game_usercontrol_key)); 478 | //ball count 479 | editor.remove(resources.getString(R.string.settings_game_ballcount_key)); 480 | 481 | editor.commit(); 482 | } 483 | 484 | /** 485 | * Reset display preferences to their defaults. 486 | */ 487 | private void loadDisplayDefaults() { 488 | final Resources resources = this.getResources(); 489 | final SharedPreferences.Editor editor = Preferences.this.getPreferenceManager().getSharedPreferences().edit(); 490 | 491 | //fps 492 | editor.remove(resources.getString(R.string.settings_display_fps_key)); 493 | //icon rows 494 | editor.remove(resources.getString(R.string.settings_display_iconrows_key)); 495 | //icon cols 496 | editor.remove(resources.getString(R.string.settings_display_iconcols_key)); 497 | //icon row spacing 498 | editor.remove(resources.getString(R.string.settings_display_rowspacing_key)); 499 | //icon col spacing 500 | editor.remove(resources.getString(R.string.settings_display_colspacing_key)); 501 | //widget locations 502 | editor.remove(resources.getString(R.string.settings_display_widgetlocations_key)); 503 | //padding top 504 | editor.remove(resources.getString(R.string.settings_display_padding_top_key)); 505 | //padding bottom 506 | editor.remove(resources.getString(R.string.settings_display_padding_bottom_key)); 507 | //padding left 508 | editor.remove(resources.getString(R.string.settings_display_padding_left_key)); 509 | //padding right 510 | editor.remove(resources.getString(R.string.settings_display_padding_right_key)); 511 | 512 | editor.commit(); 513 | } 514 | 515 | /** 516 | * Reset color preferences to their defaults. 517 | */ 518 | private void loadColorDefaults() { 519 | final Resources resources = this.getResources(); 520 | final SharedPreferences.Editor editor = Preferences.this.getPreferenceManager().getSharedPreferences().edit(); 521 | 522 | //background 523 | editor.remove(resources.getString(R.string.settings_color_background_key)); 524 | //background image 525 | editor.remove(resources.getString(R.string.settings_color_bgimage_key)); 526 | //background opacity 527 | editor.remove(resources.getString(R.string.settings_color_bgopacity_key)); 528 | //ball color 529 | editor.remove(resources.getString(R.string.settings_color_ball_key)); 530 | //block 1 color 531 | editor.remove(resources.getString(R.string.settings_color_block1_key)); 532 | //block 2 color 533 | editor.remove(resources.getString(R.string.settings_color_block2_key)); 534 | //block 3 color 535 | editor.remove(resources.getString(R.string.settings_color_block3_key)); 536 | //block style 537 | editor.remove(resources.getString(R.string.settings_color_blockstyle_key)); 538 | //ball style 539 | editor.remove(resources.getString(R.string.settings_color_ballstyle_key)); 540 | 541 | editor.commit(); 542 | } 543 | } 544 | -------------------------------------------------------------------------------- /src/com/jakewharton/breakoutwallpaper/Game.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.breakoutwallpaper; 2 | 3 | import java.util.List; 4 | import java.util.Random; 5 | import android.content.SharedPreferences; 6 | import android.content.res.Resources; 7 | import android.graphics.Bitmap; 8 | import android.graphics.BitmapFactory; 9 | import android.graphics.Canvas; 10 | import android.graphics.Paint; 11 | import android.graphics.PointF; 12 | import android.graphics.Rect; 13 | import android.graphics.RectF; 14 | import android.net.Uri; 15 | import android.util.Log; 16 | import android.widget.Toast; 17 | import com.jakewharton.utilities.WidgetLocationsPreference; 18 | 19 | public class Game implements SharedPreferences.OnSharedPreferenceChangeListener { 20 | /** 21 | * Single random number generate for this wallpaper. 22 | */ 23 | /*package*/static final Random RANDOM = new Random(); 24 | 25 | /** 26 | * Tag used for logging. 27 | */ 28 | private static final String TAG = "BreakoutWallpaper.Game"; 29 | 30 | /** 31 | * Cell value for a blank space. 32 | */ 33 | private static final int CELL_BLANK = 0; 34 | 35 | /** 36 | * Cell value for an invalid space. 37 | */ 38 | private static final int CELL_INVALID = 1; 39 | 40 | /** 41 | * Block cells between icon rows. 42 | */ 43 | private static final int CELLS_BETWEEN_ROW = 2; 44 | 45 | /** 46 | * Block cells between icon columns. 47 | */ 48 | private static final int CELLS_BETWEEN_COLUMN = 1; 49 | 50 | /** 51 | * Paint solid shapes. 52 | */ 53 | private static final int PAINT_STYLE_FILL = 0; 54 | 55 | /** 56 | * Paint shape outlines. 57 | */ 58 | private static final int PAINT_STYLE_STROKE = 1; 59 | 60 | /** 61 | * Endless mode. 62 | */ 63 | /*package*/static final int MODE_ENDLESS = 0; 64 | 65 | /** 66 | * Level mode. 67 | */ 68 | private static final int MODE_LEVELS = 1; 69 | 70 | 71 | 72 | /** 73 | * Number of cells on the board horizontally. 74 | */ 75 | private int mCellsWide; 76 | 77 | /** 78 | * Number of cells on the board vertically. 79 | */ 80 | private int mCellsTall; 81 | 82 | /** 83 | * Number of cells horizontally between the columns. 84 | */ 85 | private int mCellColumnSpacing; 86 | 87 | /** 88 | * Number of cells vertically between the rows. 89 | */ 90 | private int mCellRowSpacing; 91 | 92 | /** 93 | * Width (in pixels) of a single cell. 94 | */ 95 | private float mCellWidth; 96 | 97 | /** 98 | * Height (in pixels) of a single cell. 99 | */ 100 | private float mCellHeight; 101 | 102 | /** 103 | * Height (in pixels) of the screen. 104 | */ 105 | private int mScreenHeight; 106 | 107 | /** 108 | * Width (in pixels) of the screen. 109 | */ 110 | private int mScreenWidth; 111 | 112 | /** 113 | * Width (in pixels) of the game board. 114 | */ 115 | private int mGameWidth; 116 | 117 | /** 118 | * Height (in pixels) of the game board. 119 | */ 120 | private int mGameHeight; 121 | 122 | /** 123 | * Whether or not the screen is currently in landscape mode. 124 | */ 125 | private boolean mIsLandscape; 126 | 127 | /** 128 | * Number of icon rows on the launcher. 129 | */ 130 | private int mIconRows; 131 | 132 | /** 133 | * Number of icon columns on the launcher. 134 | */ 135 | private int mIconCols; 136 | 137 | /** 138 | * 2-dimensional array of the board's cells. 139 | * 140 | * zero == blank 141 | * non-zero == block and represents its color 142 | */ 143 | private int[][] mBoard; 144 | 145 | /** 146 | * Color of the background. 147 | */ 148 | private int mGameBackground; 149 | 150 | /** 151 | * Top padding (in pixels) of the grid from the screen top. 152 | */ 153 | private int mDotGridPaddingTop; 154 | 155 | /** 156 | * Left padding (in pixels) of the grid from the screen left. 157 | */ 158 | private int mDotGridPaddingLeft; 159 | 160 | /** 161 | * Bottom padding (in pixels) of the grid from the screen bottom. 162 | */ 163 | private int mDotGridPaddingBottom; 164 | 165 | /** 166 | * Right padding (in pixels) of the grid from the screen right. 167 | */ 168 | private int mDotGridPaddingRight; 169 | 170 | /** 171 | * Path to the user background image (if any). 172 | */ 173 | private String mBackgroundPath; 174 | 175 | /** 176 | * The user background image (if any). 177 | */ 178 | private Bitmap mBackground; 179 | 180 | /** 181 | * The size (in pixels) of a single cell. 182 | */ 183 | private final RectF mCellSize; 184 | 185 | /** 186 | * The locations of widgets on the launcher. 187 | */ 188 | private List mWidgetLocations; 189 | 190 | /** 191 | * Paint to draw the background color. 192 | */ 193 | private final Paint mBackgroundPaint; 194 | 195 | /** 196 | * Paint to draw the blocks. 197 | */ 198 | private final Paint mBlockForeground; 199 | 200 | /** 201 | * Paint to draw the balls. 202 | */ 203 | private final Paint mBallForeground; 204 | 205 | /** 206 | * Balls. Enough said. 207 | */ 208 | private Ball[] mBalls; 209 | 210 | /** 211 | * Colors for blocks. 212 | */ 213 | private final int[] mBlockColors; 214 | 215 | /** 216 | * Number of blocks remaining in the game. 217 | */ 218 | private int mBlocksRemaining; 219 | 220 | /** 221 | * Total blocks in a level. 222 | */ 223 | private int mBlocksTotal; 224 | 225 | /** 226 | * Gameplay mode. 227 | */ 228 | private int mMode; 229 | 230 | /** 231 | * Percentage at which to regenerate blocks. 232 | */ 233 | private float mRegenPercent; 234 | 235 | 236 | 237 | /** 238 | * Create a new game. 239 | */ 240 | public Game() { 241 | if (Wallpaper.LOG_VERBOSE) { 242 | Log.v(Game.TAG, "> Game()"); 243 | } 244 | 245 | //Create Paints 246 | this.mBackgroundPaint = new Paint(); 247 | this.mBlockForeground = new Paint(Paint.ANTI_ALIAS_FLAG); 248 | this.mBallForeground = new Paint(Paint.ANTI_ALIAS_FLAG); 249 | 250 | this.mCellSize = new RectF(0, 0, 0, 0); 251 | 252 | this.mBlockColors = new int[3]; 253 | 254 | //Load all preferences or their defaults 255 | Wallpaper.PREFERENCES.registerOnSharedPreferenceChangeListener(this); 256 | this.onSharedPreferenceChanged(Wallpaper.PREFERENCES, null); 257 | 258 | if (Wallpaper.LOG_VERBOSE) { 259 | Log.v(Game.TAG, "< Game()"); 260 | } 261 | } 262 | 263 | 264 | 265 | /** 266 | * Handle the changing of a preference. 267 | */ 268 | public void onSharedPreferenceChanged(final SharedPreferences preferences, final String key) { 269 | if (Wallpaper.LOG_VERBOSE) { 270 | Log.v(Game.TAG, "> onSharedPreferenceChanged()"); 271 | } 272 | 273 | final boolean all = (key == null); 274 | final Resources resources = Wallpaper.CONTEXT.getResources(); 275 | 276 | boolean hasLayoutChanged = false; 277 | boolean hasGraphicsChanged = false; 278 | boolean hasBallsChanged = false; 279 | 280 | 281 | // GENERAL // 282 | 283 | int balls = 0; 284 | final String ghostCount = resources.getString(R.string.settings_game_ballcount_key); 285 | if (all || key.equals(ghostCount)) { 286 | balls = preferences.getInt(ghostCount, resources.getInteger(R.integer.game_ballcount_default)); 287 | hasBallsChanged = true; 288 | 289 | if (Wallpaper.LOG_DEBUG) { 290 | Log.d(Game.TAG, "Ball Count: " + balls); 291 | } 292 | 293 | this.mBalls = new Ball[balls]; 294 | for (int i = 0; i < balls; i++) { 295 | this.mBalls[i] = new Ball(); 296 | } 297 | } 298 | 299 | final String gameMode = resources.getString(R.string.settings_game_mode_key); 300 | if (all || key.equals(gameMode)) { 301 | this.mMode = preferences.getInt(gameMode, resources.getInteger(R.integer.game_mode_default)); 302 | 303 | if (Wallpaper.LOG_DEBUG) { 304 | Log.d(Game.TAG, "Game Mode: " + this.mMode); 305 | } 306 | } 307 | 308 | final String endlessRegen = resources.getString(R.string.settings_game_endlessregen_key); 309 | if (all || key.equals(endlessRegen)) { 310 | final int regen = preferences.getInt(endlessRegen, resources.getInteger(R.integer.game_endlessregen_default)); 311 | this.mRegenPercent = regen / 100.0f; 312 | 313 | if (Wallpaper.LOG_DEBUG) { 314 | Log.d(Game.TAG, "Endless Regen: " + regen + "%"); 315 | } 316 | } 317 | 318 | 319 | // COLORS // 320 | 321 | final String gameBackground = resources.getString(R.string.settings_color_background_key); 322 | if (all || key.equals(gameBackground)) { 323 | this.mGameBackground = preferences.getInt(gameBackground, resources.getInteger(R.integer.color_background_default)); 324 | 325 | if (Wallpaper.LOG_DEBUG) { 326 | Log.d(Game.TAG, "Background: #" + Integer.toHexString(this.mGameBackground)); 327 | } 328 | } 329 | 330 | final String backgroundImage = resources.getString(R.string.settings_color_bgimage_key); 331 | if (all || key.equals(backgroundImage)) { 332 | this.mBackgroundPath = preferences.getString(backgroundImage, null); 333 | 334 | if (this.mBackgroundPath != null) { 335 | if (Wallpaper.LOG_DEBUG) { 336 | Log.d(Game.TAG, "Background Image: " + this.mBackgroundPath); 337 | } 338 | 339 | //Trigger performResize 340 | hasGraphicsChanged = true; 341 | } else { 342 | this.mBackground = null; 343 | } 344 | } 345 | 346 | final String backgroundOpacity = resources.getString(R.string.settings_color_bgopacity_key); 347 | if (all || key.equals(backgroundOpacity)) { 348 | this.mBackgroundPaint.setAlpha(preferences.getInt(backgroundOpacity, resources.getInteger(R.integer.color_bgopacity_default))); 349 | 350 | if (Wallpaper.LOG_DEBUG) { 351 | Log.d(Game.TAG, "Background Image Opacity: " + this.mBackgroundPaint.getAlpha()); 352 | } 353 | } 354 | 355 | final String ballColor = resources.getString(R.string.settings_color_ball_key); 356 | if (all || key.equals(ballColor)) { 357 | this.mBallForeground.setColor(preferences.getInt(ballColor, resources.getInteger(R.integer.color_ball_default))); 358 | 359 | if (Wallpaper.LOG_DEBUG) { 360 | Log.d(Game.TAG, "Ball Color: #" + Integer.toHexString(this.mBallForeground.getColor())); 361 | } 362 | } 363 | 364 | final String block1Color = resources.getString(R.string.settings_color_block1_key); 365 | if (all || key.equals(block1Color)) { 366 | this.mBlockColors[0] = preferences.getInt(block1Color, resources.getInteger(R.integer.color_block1_default)); 367 | 368 | if (Wallpaper.LOG_DEBUG) { 369 | Log.d(Game.TAG, "Block 1 Color: #" + Integer.toHexString(this.mBlockColors[0])); 370 | } 371 | } 372 | 373 | final String block2Color = resources.getString(R.string.settings_color_block2_key); 374 | if (all || key.equals(block2Color)) { 375 | this.mBlockColors[1] = preferences.getInt(block2Color, resources.getInteger(R.integer.color_block2_default)); 376 | 377 | if (Wallpaper.LOG_DEBUG) { 378 | Log.d(Game.TAG, "Block 2 Color: #" + Integer.toHexString(this.mBlockColors[1])); 379 | } 380 | } 381 | 382 | final String block3Color = resources.getString(R.string.settings_color_block3_key); 383 | if (all || key.equals(block3Color)) { 384 | this.mBlockColors[2] = preferences.getInt(block3Color, resources.getInteger(R.integer.color_block3_default)); 385 | 386 | if (Wallpaper.LOG_DEBUG) { 387 | Log.d(Game.TAG, "Block 3 Color: #" + Integer.toHexString(this.mBlockColors[2])); 388 | } 389 | } 390 | 391 | final String blockStyle = resources.getString(R.string.settings_color_blockstyle_key); 392 | if (all || key.equals(blockStyle)) { 393 | final int blockStyleValue = preferences.getInt(blockStyle, resources.getInteger(R.integer.color_blockstyle_default)); 394 | switch (blockStyleValue) { 395 | case Game.PAINT_STYLE_FILL: 396 | this.mBlockForeground.setStyle(Paint.Style.FILL); 397 | 398 | if (Wallpaper.LOG_DEBUG) { 399 | Log.d(Game.TAG, "Block Style: FILL"); 400 | } 401 | break; 402 | 403 | case Game.PAINT_STYLE_STROKE: 404 | this.mBlockForeground.setStyle(Paint.Style.STROKE); 405 | 406 | if (Wallpaper.LOG_DEBUG) { 407 | Log.d(Game.TAG, "Block Style: STROKE"); 408 | } 409 | break; 410 | 411 | default: 412 | Log.e(Game.TAG, "Invalid block style value " + blockStyleValue); 413 | } 414 | } 415 | 416 | final String ballStyle = resources.getString(R.string.settings_color_ballstyle_key); 417 | if (all || key.equals(ballStyle)) { 418 | final int ballStyleValue = preferences.getInt(ballStyle, resources.getInteger(R.integer.color_ballstyle_default)); 419 | switch (ballStyleValue) { 420 | case Game.PAINT_STYLE_FILL: 421 | this.mBallForeground.setStyle(Paint.Style.FILL); 422 | 423 | if (Wallpaper.LOG_DEBUG) { 424 | Log.d(Game.TAG, "Ball Style: FILL"); 425 | } 426 | break; 427 | 428 | case Game.PAINT_STYLE_STROKE: 429 | this.mBallForeground.setStyle(Paint.Style.STROKE); 430 | 431 | if (Wallpaper.LOG_DEBUG) { 432 | Log.d(Game.TAG, "Ball Style: STROKE"); 433 | } 434 | break; 435 | 436 | default: 437 | Log.e(Game.TAG, "Invalid ball style value " + ballStyleValue); 438 | } 439 | } 440 | 441 | 442 | // GRID // 443 | 444 | final String dotGridPaddingLeft = resources.getString(R.string.settings_display_padding_left_key); 445 | if (all || key.equals(dotGridPaddingLeft)) { 446 | this.mDotGridPaddingLeft = preferences.getInt(dotGridPaddingLeft, resources.getInteger(R.integer.display_padding_left_default)); 447 | hasGraphicsChanged = true; 448 | 449 | if (Wallpaper.LOG_DEBUG) { 450 | Log.d(Game.TAG, "Dot Grid Padding Left: " + this.mDotGridPaddingLeft); 451 | } 452 | } 453 | 454 | final String dotGridPaddingRight = resources.getString(R.string.settings_display_padding_right_key); 455 | if (all || key.equals(dotGridPaddingRight)) { 456 | this.mDotGridPaddingRight = preferences.getInt(dotGridPaddingRight, resources.getInteger(R.integer.display_padding_right_default)); 457 | hasGraphicsChanged = true; 458 | 459 | if (Wallpaper.LOG_DEBUG) { 460 | Log.d(Game.TAG, "Dot Grid Padding Right: " + this.mDotGridPaddingRight); 461 | } 462 | } 463 | 464 | final String dotGridPaddingTop = resources.getString(R.string.settings_display_padding_top_key); 465 | if (all || key.equals(dotGridPaddingTop)) { 466 | this.mDotGridPaddingTop = preferences.getInt(dotGridPaddingTop, resources.getInteger(R.integer.display_padding_top_default)); 467 | hasGraphicsChanged = true; 468 | 469 | if (Wallpaper.LOG_DEBUG) { 470 | Log.d(Game.TAG, "Dot Grid Padding Top: " + this.mDotGridPaddingTop); 471 | } 472 | } 473 | 474 | final String dotGridPaddingBottom = resources.getString(R.string.settings_display_padding_bottom_key); 475 | if (all || key.equals(dotGridPaddingBottom)) { 476 | this.mDotGridPaddingBottom = preferences.getInt(dotGridPaddingBottom, resources.getInteger(R.integer.display_padding_bottom_default)); 477 | hasGraphicsChanged = true; 478 | 479 | if (Wallpaper.LOG_DEBUG) { 480 | Log.d(Game.TAG, "Dot Grid Padding Bottom: " + this.mDotGridPaddingBottom); 481 | } 482 | } 483 | 484 | final String widgetLocations = resources.getString(R.string.settings_display_widgetlocations_key); 485 | if (all || key.equals(widgetLocations)) { 486 | this.mWidgetLocations = WidgetLocationsPreference.convertStringToWidgetList(preferences.getString(widgetLocations, resources.getString(R.string.display_widgetlocations_default))); 487 | hasLayoutChanged = true; 488 | 489 | if (Wallpaper.LOG_DEBUG) { 490 | Log.d(Game.TAG, "Widget Locations: " + (this.mWidgetLocations.size() / 4)); 491 | } 492 | } 493 | 494 | 495 | // CELLS // 496 | 497 | final String iconRows = resources.getString(R.string.settings_display_iconrows_key); 498 | if (all || key.equals(iconRows)) { 499 | this.mIconRows = preferences.getInt(iconRows, resources.getInteger(R.integer.display_iconrows_default)); 500 | hasLayoutChanged = true; 501 | 502 | if (Wallpaper.LOG_DEBUG) { 503 | Log.d(Game.TAG, "Icon Rows: " + this.mIconRows); 504 | } 505 | } 506 | 507 | final String iconCols = resources.getString(R.string.settings_display_iconcols_key); 508 | if (all || key.equals(iconCols)) { 509 | this.mIconCols = preferences.getInt(iconCols, resources.getInteger(R.integer.display_iconcols_default)); 510 | hasLayoutChanged = true; 511 | 512 | if (Wallpaper.LOG_DEBUG) { 513 | Log.d(Game.TAG, "Icon Cols: " + this.mIconCols); 514 | } 515 | } 516 | 517 | final String cellSpacingRow = resources.getString(R.string.settings_display_rowspacing_key); 518 | if (all || key.equals(cellSpacingRow)) { 519 | this.mCellRowSpacing = preferences.getInt(cellSpacingRow, resources.getInteger(R.integer.display_rowspacing_default)); 520 | hasLayoutChanged = true; 521 | 522 | if (Wallpaper.LOG_DEBUG) { 523 | Log.d(Game.TAG, "Cell Row Spacing: " + this.mCellRowSpacing); 524 | } 525 | } 526 | 527 | final String cellSpacingCol = resources.getString(R.string.settings_display_colspacing_key); 528 | if (all || key.equals(cellSpacingCol)) { 529 | this.mCellColumnSpacing = preferences.getInt(cellSpacingCol, resources.getInteger(R.integer.display_colspacing_default)); 530 | hasLayoutChanged = true; 531 | 532 | if (Wallpaper.LOG_DEBUG) { 533 | Log.d(Game.TAG, "Cell Column Spacing: " + this.mCellColumnSpacing); 534 | } 535 | } 536 | 537 | if (hasLayoutChanged) { 538 | this.mCellsWide = (this.mIconCols * (this.mCellColumnSpacing + Game.CELLS_BETWEEN_COLUMN)) + Game.CELLS_BETWEEN_COLUMN; 539 | this.mCellsTall = (this.mIconRows * (this.mCellRowSpacing + Game.CELLS_BETWEEN_ROW)) + Game.CELLS_BETWEEN_ROW; 540 | 541 | if (Wallpaper.LOG_DEBUG) { 542 | Log.d(Game.TAG, "Cells Wide: " + this.mCellsWide); 543 | Log.d(Game.TAG, "Cells Tall: " + this.mCellsTall); 544 | } 545 | 546 | //Create playing board 547 | this.mBoard = new int[this.mCellsTall][this.mCellsWide]; 548 | } 549 | if (hasLayoutChanged || hasGraphicsChanged || hasBallsChanged) { 550 | if ((this.mScreenWidth > 0) && (this.mScreenHeight > 0)) { 551 | //Resize everything to fit 552 | this.performResize(this.mScreenWidth, this.mScreenHeight); 553 | } 554 | 555 | this.newLevel(); 556 | } 557 | 558 | if (Wallpaper.LOG_VERBOSE) { 559 | Log.v(Game.TAG, "< onSharedPreferenceChanged()"); 560 | } 561 | } 562 | 563 | /** 564 | * Get the width of a cell. 565 | * 566 | * @return Cell width. 567 | */ 568 | public float getCellWidth() { 569 | return this.mCellWidth; 570 | } 571 | 572 | /** 573 | * Get the height of a cell. 574 | * 575 | * @return Cell height. 576 | */ 577 | public float getCellHeight() { 578 | return this.mCellHeight; 579 | } 580 | 581 | /** 582 | * Determine whether or not a position is a valid cell. 583 | * 584 | * @param x X coordinate. 585 | * @param y Y coordinate. 586 | * @return Boolean. 587 | */ 588 | private boolean isCell(final int x, final int y) { 589 | return (x >= 0) && (x < this.mCellsWide) 590 | && (y >= 0) && (y < this.mCellsTall) 591 | && (this.mBoard[y][x] != Game.CELL_INVALID); 592 | } 593 | 594 | /** 595 | * Determine whether or not a position contains a block. 596 | * 597 | * @param x X coordinate. 598 | * @param y Y coordinate. 599 | * @return Boolean. 600 | */ 601 | private boolean isBlock(final int x, final int y) { 602 | return this.isCell(x, y) && (this.mBoard[y][x] != Game.CELL_BLANK); 603 | } 604 | 605 | /** 606 | * Manipulate a ball direction based on a user touch. 607 | * 608 | * @param x X coordinate of touch. 609 | * @param y Y coordinate of touch. 610 | */ 611 | public void setTouch(final float x, final float y) { 612 | double closestDistance = Float.MAX_VALUE; 613 | Ball closestBall = null; 614 | for (final Ball ball : this.mBalls) { 615 | final double ballDistance = Math.sqrt(Math.pow(x - ball.getLocationX(), 2) + Math.pow(y - ball.getLocationY(), 2)); 616 | if (ballDistance < closestDistance) { 617 | closestBall = ball; 618 | closestDistance = ballDistance; 619 | } 620 | } 621 | 622 | closestBall.setVector(x - closestBall.getLocationX(), y - closestBall.getLocationY()); 623 | } 624 | 625 | /** 626 | * Reset the game state to that of first initialization. 627 | */ 628 | public void newLevel() { 629 | if (Wallpaper.LOG_VERBOSE) { 630 | Log.v(Game.TAG, "> newGame()"); 631 | } 632 | 633 | //Initialize board 634 | final int iconCellsWidth = this.mCellColumnSpacing + Game.CELLS_BETWEEN_COLUMN; 635 | final int iconCellsHeight = this.mCellRowSpacing + Game.CELLS_BETWEEN_ROW; 636 | final int colors = this.mBlockColors.length; 637 | for (int y = 0; y < this.mCellsTall; y++) { 638 | for (int x = 0; x < this.mCellsWide; x++) { 639 | final int dx = x % iconCellsWidth; 640 | final int dy = y % iconCellsHeight; 641 | if ((dx < Game.CELLS_BETWEEN_COLUMN) || (dy < Game.CELLS_BETWEEN_ROW)) { 642 | this.mBoard[y][x] = this.mBlockColors[(x + y) % colors]; 643 | } else { 644 | this.mBoard[y][x] = Game.CELL_INVALID; 645 | } 646 | } 647 | } 648 | 649 | //Remove board under widgets 650 | for (final Rect widget : this.mWidgetLocations) { 651 | if (Wallpaper.LOG_DEBUG) { 652 | Log.d(Game.TAG, "Widget: L=" + widget.left + ", T=" + widget.top + ", R=" + widget.right + ", B=" + widget.bottom); 653 | } 654 | 655 | final int left = (widget.left * iconCellsWidth) + Game.CELLS_BETWEEN_COLUMN; 656 | final int top = (widget.top * iconCellsHeight) + Game.CELLS_BETWEEN_ROW; 657 | final int bottom = (widget.bottom * iconCellsHeight) + Game.CELLS_BETWEEN_ROW + this.mCellRowSpacing - 1; 658 | final int right = (widget.right * iconCellsWidth) + Game.CELLS_BETWEEN_COLUMN + this.mCellColumnSpacing - 1; 659 | for (int y = top; y <= bottom; y++) { 660 | for (int x = left; x <= right; x++) { 661 | this.mBoard[y][x] = Game.CELL_INVALID; 662 | } 663 | } 664 | } 665 | 666 | //Count blocks 667 | this.mBlocksRemaining = 0; 668 | for (int y = 0; y < this.mCellsTall; y++) { 669 | for (int x = 0; x < this.mCellsWide; x++) { 670 | if ((this.mBoard[y][x] != Game.CELL_BLANK) && (this.mBoard[y][x] != Game.CELL_INVALID)) { 671 | this.mBlocksRemaining += 1; 672 | } 673 | } 674 | } 675 | this.mBlocksTotal = this.mBlocksRemaining; 676 | 677 | if (Wallpaper.LOG_VERBOSE) { 678 | Log.v(Game.TAG, "< newGame()"); 679 | } 680 | } 681 | 682 | /** 683 | * Convert an icon position to on-screen coordinates 684 | * 685 | * @param x Icon column 686 | * @param y Icon row 687 | * @return Screen coordinates. 688 | */ 689 | private PointF getBallLocationAtIcon(final int x, final int y) { 690 | return new PointF( 691 | ((this.mCellColumnSpacing * x) + (Game.CELLS_BETWEEN_COLUMN * (x + 1)) + (this.mCellColumnSpacing / 2.0f)) * this.mCellWidth, 692 | ((this.mCellRowSpacing * y) + (Game.CELLS_BETWEEN_ROW * (y + 1)) + (this.mCellRowSpacing / 2.0f)) * this.mCellHeight 693 | ); 694 | } 695 | 696 | /** 697 | * Iterate all entities one step. 698 | */ 699 | public void tick() { 700 | for (final Ball ball : this.mBalls) { 701 | ball.tick(); 702 | 703 | //Test screen edges 704 | if (ball.getLocationX() <= 0) { 705 | ball.setVector(Math.abs(ball.getVectorX()), ball.getVectorY() + Game.RANDOM.nextFloat()); 706 | } else if (ball.getLocationX() >= this.mGameWidth) { 707 | ball.setVector(-Math.abs(ball.getVectorX()), ball.getVectorY() + Game.RANDOM.nextFloat()); 708 | } 709 | if (ball.getLocationY() <= 0) { 710 | ball.setVector(ball.getVectorX() + Game.RANDOM.nextFloat(), Math.abs(ball.getVectorY())); 711 | } else if (ball.getLocationY() >= this.mGameHeight) { 712 | ball.setVector(ball.getVectorX() + Game.RANDOM.nextFloat(), -Math.abs(ball.getVectorY())); 713 | } 714 | 715 | //Test blocks 716 | final int ballCheck1X = (int)((ball.getLocationX() - Ball.RADIUS) / this.mCellWidth); 717 | final int ballCheck1Y = (int)((ball.getLocationY() + (Math.signum(ball.getVectorY()) * Ball.RADIUS)) / this.mCellHeight); 718 | final int ballCheck2X = (int)((ball.getLocationX() + Ball.RADIUS) / this.mCellWidth); 719 | final int ballCheck2Y = ballCheck1Y; 720 | final int ballCheck3X = (int)((ball.getLocationX() + (Math.signum(ball.getVectorX()) * Ball.RADIUS)) / this.mCellWidth); 721 | final int ballCheck3Y = (int)((ball.getLocationY() + (-Math.signum(ball.getVectorY()) * Ball.RADIUS)) / this.mCellHeight); 722 | this.checkCollision(ball, ballCheck1X, ballCheck1Y); 723 | this.checkCollision(ball, ballCheck2X, ballCheck2Y); 724 | this.checkCollision(ball, ballCheck3X, ballCheck3Y); 725 | 726 | //Check game mode 727 | switch (this.mMode) { 728 | case Game.MODE_ENDLESS: 729 | if (this.mBlocksRemaining < (this.mBlocksTotal * this.mRegenPercent)) { 730 | while (true) { 731 | final int x = Game.RANDOM.nextInt(this.mCellsWide); 732 | final int y = Game.RANDOM.nextInt(this.mCellsTall); 733 | 734 | if (this.isCell(x, y) && (this.mBoard[y][x] == Game.CELL_BLANK)) { 735 | this.mBoard[y][x] = this.mBlockColors[(x + y) % this.mBlockColors.length]; 736 | break; 737 | } 738 | } 739 | this.mBlocksRemaining += 1; 740 | } 741 | break; 742 | 743 | case Game.MODE_LEVELS: 744 | if (this.mBlocksRemaining == 0) { 745 | this.newLevel(); 746 | } 747 | break; 748 | 749 | default: 750 | Log.e(Game.TAG, "Invalid game mode value " + this.mMode); 751 | break; 752 | } 753 | } 754 | 755 | if (this.mBlocksRemaining <= 0) { 756 | this.newLevel(); 757 | } 758 | } 759 | 760 | /** 761 | * Determine if a ball has collided with a block in the specified coordinates. 762 | * 763 | * @param ball Ball instance. 764 | * @param blockX X coordinate of potential block. 765 | * @param blockY Y coordinate of potential block. 766 | * @return Boolean indicating collision. 767 | */ 768 | private boolean checkCollision(final Ball ball, final int blockX, final int blockY) 769 | { 770 | if (Wallpaper.LOG_VERBOSE) { 771 | Log.d(Game.TAG, "Checking block (" + blockX + "," + blockY + ") against ball at (" + ball.getLocationX() + "," + ball.getLocationY() + ")"); 772 | } 773 | 774 | if (!this.isBlock(blockX, blockY)) { 775 | return false; 776 | } 777 | 778 | if (Wallpaper.LOG_VERBOSE) { 779 | Log.d(Game.TAG, "-- Is Collision"); 780 | Log.d(Game.TAG, "-- Current Vector: (" + ball.getVectorX() + "," + ball.getVectorY() + ")"); 781 | } 782 | 783 | final float cellWidthOverTwo = this.mCellWidth / 2; 784 | final float cellHeightOverTwo = this.mCellHeight / 2; 785 | final float blockCenterX = (blockX * this.mCellWidth) + cellWidthOverTwo; 786 | final float blockCenterY = (blockY * this.mCellHeight) + cellHeightOverTwo; 787 | 788 | //Calculate collision unit vector 789 | float collisionUnitVectorX = blockCenterX - ball.getLocationX(); 790 | float collisionUnitVectorY = blockCenterY - ball.getLocationY(); 791 | final float collisionVectorLength = (float)Math.sqrt(Math.pow(collisionUnitVectorX, 2) + Math.pow(collisionUnitVectorY, 2)); 792 | collisionUnitVectorX /= collisionVectorLength; 793 | collisionUnitVectorY /= collisionVectorLength; 794 | 795 | //Calculate ball velocity unit vector 796 | final float ballVectorLength = (float)Math.sqrt(Math.pow(ball.getVectorX(), 2) + Math.pow(ball.getVectorY(), 2)); 797 | final float ballUnitVectorX = ball.getVectorX() / ballVectorLength; 798 | final float ballUnitVectorY = ball.getVectorY() / ballVectorLength; 799 | 800 | final float dotProduct = (collisionUnitVectorX * ballUnitVectorX) + (collisionUnitVectorY * ballUnitVectorY); 801 | final float vectorDeltaX = -2 * collisionUnitVectorX * dotProduct * ballVectorLength; 802 | final float vectorDeltaY = -2 * collisionUnitVectorY * dotProduct * ballVectorLength; 803 | 804 | float newVectorX = ball.getVectorX() + vectorDeltaX; 805 | float newVectorY = ball.getVectorY() + vectorDeltaY; 806 | final float newVectorLength = (float)Math.sqrt(Math.pow(newVectorX, 2) + Math.pow(newVectorY, 2)); 807 | newVectorX /= newVectorLength; 808 | newVectorY /= newVectorLength; 809 | 810 | ball.setVector(newVectorX, newVectorY); 811 | 812 | if (Wallpaper.LOG_VERBOSE) { 813 | Log.d(Game.TAG, "-- New Vector: (" + ball.getVectorX() + "," + ball.getVectorY() + ")"); 814 | } 815 | 816 | this.mBoard[blockY][blockX] = Game.CELL_BLANK; 817 | this.mBlocksRemaining -= 1; 818 | 819 | return true; 820 | } 821 | 822 | /** 823 | * Resize the game board and all entities according to a new width and height. 824 | * 825 | * @param screenWidth New width. 826 | * @param screenHeight New height. 827 | */ 828 | public void performResize(int screenWidth, int screenHeight) { 829 | if (Wallpaper.LOG_VERBOSE) { 830 | Log.v(Game.TAG, "> performResize(width = " + screenWidth + ", height = " + screenHeight + ")"); 831 | } 832 | 833 | //Background image 834 | if (this.mBackgroundPath != null) { 835 | try { 836 | final Bitmap temp = BitmapFactory.decodeStream(Wallpaper.CONTEXT.getContentResolver().openInputStream(Uri.parse(this.mBackgroundPath))); 837 | final float pictureAR = temp.getWidth() / (temp.getHeight() * 1.0f); 838 | final float screenAR = screenWidth / (screenHeight * 1.0f); 839 | int newWidth; 840 | int newHeight; 841 | int x; 842 | int y; 843 | 844 | if (pictureAR > screenAR) { 845 | //wider than tall related to the screen AR 846 | newHeight = screenHeight; 847 | newWidth = (int)(temp.getWidth() * (screenHeight / (temp.getHeight() * 1.0f))); 848 | x = (newWidth - screenWidth) / 2; 849 | y = 0; 850 | } else { 851 | //taller than wide related to the screen AR 852 | newWidth = screenWidth; 853 | newHeight = (int)(temp.getHeight() * (screenWidth / (temp.getWidth() * 1.0f))); 854 | x = 0; 855 | y = (newHeight - screenHeight) / 2; 856 | } 857 | 858 | final Bitmap scaled = Bitmap.createScaledBitmap(temp, newWidth, newHeight, false); 859 | this.mBackground = Bitmap.createBitmap(scaled, x, y, screenWidth, screenHeight); 860 | } catch (final Exception e) { 861 | e.printStackTrace(); 862 | Log.w(Game.TAG, "Unable to load background bitmap."); 863 | Toast.makeText(Wallpaper.CONTEXT, "Unable to load background bitmap.", Toast.LENGTH_SHORT).show(); 864 | this.mBackground = null; 865 | } 866 | } 867 | 868 | this.mIsLandscape = (screenWidth > screenHeight); 869 | this.mScreenWidth = screenWidth; 870 | this.mScreenHeight = screenHeight; 871 | 872 | if (this.mIsLandscape) { 873 | this.mGameWidth = (screenWidth - (this.mDotGridPaddingLeft + this.mDotGridPaddingRight + this.mDotGridPaddingBottom)); 874 | this.mGameHeight = (screenHeight - this.mDotGridPaddingTop); 875 | } else { 876 | this.mGameWidth = (screenWidth - (this.mDotGridPaddingLeft + this.mDotGridPaddingRight)); 877 | this.mGameHeight = (screenHeight - (this.mDotGridPaddingTop + this.mDotGridPaddingBottom)); 878 | } 879 | 880 | //Update cell size 881 | this.mCellWidth = this.mGameWidth / (this.mCellsWide * 1.0f); 882 | this.mCellHeight = this.mGameHeight / (this.mCellsTall * 1.0f); 883 | this.mCellSize.right = this.mCellWidth; 884 | this.mCellSize.bottom = this.mCellHeight; 885 | 886 | //Set ball radius 887 | Ball.RADIUS = ((this.mCellWidth < this.mCellHeight) ? this.mCellWidth : this.mCellHeight) * Ball.SIZE_PERCENTAGE / 2; 888 | 889 | //Position balls 890 | final PointF ball0Location = this.getBallLocationAtIcon(0, 0); 891 | this.mBalls[0].setLocation(ball0Location.x, ball0Location.y); 892 | this.mBalls[0].setVector(0, -1); 893 | if (this.mBalls.length > 1) { 894 | final PointF ball1Location = this.getBallLocationAtIcon(this.mIconCols - 1, this.mIconRows - 1); 895 | this.mBalls[1].setLocation(ball1Location.x, ball1Location.y); 896 | this.mBalls[1].setVector(0, 1); 897 | } 898 | if (this.mBalls.length > 2) { 899 | final PointF ball2Location = this.getBallLocationAtIcon(this.mIconCols - 1, 0); 900 | this.mBalls[2].setLocation(ball2Location.x, ball2Location.y); 901 | this.mBalls[2].setVector(1, 0); 902 | } 903 | if (this.mBalls.length > 3) { 904 | final PointF ball3Location = this.getBallLocationAtIcon(0, this.mIconRows - 1); 905 | this.mBalls[3].setLocation(ball3Location.x, ball3Location.y); 906 | this.mBalls[3].setVector(-1, 0); 907 | } 908 | 909 | if (Wallpaper.LOG_DEBUG) { 910 | Log.d(Game.TAG, "Is Landscape: " + this.mIsLandscape); 911 | Log.d(Game.TAG, "Screen Width: " + screenWidth); 912 | Log.d(Game.TAG, "Screen Height: " + screenHeight); 913 | Log.d(Game.TAG, "Cell Width: " + this.mCellWidth); 914 | Log.d(Game.TAG, "Cell Height: " + this.mCellHeight); 915 | Log.d(Game.TAG, "Ball Radius: " + Ball.RADIUS); 916 | } 917 | 918 | if (Wallpaper.LOG_VERBOSE) { 919 | Log.v(Game.TAG, "< performResize()"); 920 | } 921 | } 922 | 923 | /** 924 | * Render the board and all entities on a Canvas. 925 | * 926 | * @param c Canvas to draw on. 927 | */ 928 | public void draw(final Canvas c) { 929 | c.save(); 930 | 931 | //Clear the screen in case of transparency in the image 932 | c.drawColor(this.mGameBackground); 933 | if (this.mBackground != null) { 934 | //Bitmap should already be sized to the screen so draw it at the origin 935 | c.drawBitmap(this.mBackground, 0, 0, this.mBackgroundPaint); 936 | } 937 | 938 | //Align the Canvas 939 | c.translate(this.mDotGridPaddingLeft, this.mDotGridPaddingTop); 940 | 941 | //Draw blocks 942 | for (int y = 0; y < this.mCellsTall; y++) { 943 | for (int x = 0; x < this.mCellsWide; x++) { 944 | final int cell = this.mBoard[y][x]; 945 | if ((cell != Game.CELL_BLANK) && (cell != Game.CELL_INVALID)) { 946 | this.mBlockForeground.setColor(cell); 947 | 948 | final float left = x * this.mCellWidth; 949 | final float top = y * this.mCellHeight; 950 | final float right = left + this.mCellWidth; 951 | final float bottom = top + this.mCellHeight; 952 | 953 | c.drawRect(left, top, right, bottom, this.mBlockForeground); 954 | } 955 | } 956 | } 957 | 958 | //Draw balls 959 | for (final Ball ball : this.mBalls) { 960 | c.drawRect(ball.getLocationX() - Ball.RADIUS, ball.getLocationY() - Ball.RADIUS, ball.getLocationX() + Ball.RADIUS, ball.getLocationY() + Ball.RADIUS, this.mBallForeground); 961 | } 962 | 963 | c.restore(); 964 | } 965 | } 966 | --------------------------------------------------------------------------------