├── .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 |
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 | rather than rating negatively in the app comments. Enjoy!
20 |
21 | [](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 |
--------------------------------------------------------------------------------