Thursday, August 29, 2013

Adding preferences to your wallpaper

This blog will detail how to add a preferences to your wallpaper. We are going to modify the FallingRosePetals wallpaper that was created in a previous blog. It may help if you look over that tutorial first.

The full source code is available at github https://github.com/RealWorldApplications/LiveWallpaperPrefs

A quick overview of the steps we will take to modify the live wallpaper.
1. Create a file wallpaper.xml.
2. Create a file prefs.xml.
3. Modify the strings.xml file.
4. Modify the Android manifest.
5. Create a new class LiveWallpaperPreferences.java
6. Create a new class LiveWallpaperSettings.java
7. Modify the class LiveWallpaperServices.java to utilize the user changes.

This demonstration allows the user to change the background color. From this sample you should be able to implement any preferences to customize your app. First off I have to give credit to the book "Andengine Game Programming", by Jayme Schroeder and Brian Broyles. I highly recommend reading the book if you wand to create live wallpapers and games using andengine.

First create a file wallpaper.xml in the folder xml under the folder res. If necessary create the xml folder.
Add this code to the file:
<?xml version="1.0" encoding="UTF-8"?>
<wallpaper
        xmlns:android="http://schemas.android.com/apk/res/android"  
        android:thumbnail="@drawable/logo"
        android:description="@string/wallpaper_description"
        android:settingsActivity="com.ceecee.android.live.LiveWallpaperSettings"
        />
We also need to create a layout view for the available choices in background settings. Create a file prefs.xml in the layout folder. We will create a radio group with radio buttons offering the available choices in background colors.
Add this code to the newly created file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="top"
    android:orientation="vertical" >

   <TextView 
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textAppearance="?android:attr/textAppearanceMedium"
      
       android:text="@string/radio_background"/>
   
        <RadioGroup 
            android:id="@+id/radioBackground"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <RadioButton
                android:id="@+id/radioBlack"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/radio_black" />

          <RadioButton
             android:id="@+id/radioGreen"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/radio_green"
             />
          <RadioButton
             android:id="@+id/radioBlue"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/radio_blue"
             />
         </RadioGroup>
         
 

</LinearLayout>

We will need to edit the strings.xml file for the values used by our radio buttons.
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Falling Rose Petals With Prefs</string>
    <string name="wallpaper_description">Rose Petals fall from top of screen</string>
    <string name="live_wallpaper_settings">Wallpaper Settings</string>
    <string name="radio_background">Background Color</string>
    <string name="radio_black">Black</string>
    <string name="radio_green">Green</string>
    <string name="radio_blue">Blue</string>
</resources>
Next edit the manifest so that is uses the settings file.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ceecee.android.live"
    android:versionCode="1"
    android:versionName="1.0" >

     <uses-sdk  android:minSdkVersion="8"
        android:targetSdkVersion="17" />
    <uses-feature  
        android:glEsVersion="0x00020000"
        android:required="true"
        android:name="android.software.live_wallpaper">
    </uses-feature>

    <application
        android:icon="@drawable/logo"
        android:label="@string/app_name" >
        <!--  Live Wallpaper Service -->
        <service android:name="com.ceecee.android.live.LiveWallpaperService"
            android:enabled="true"
            android:permission="android.permission.BIND_WALLPAPER" 
            android:icon="@drawable/logo" 
            android:description="@string/wallpaper_description">
            <intent-filter android:priority="1">
                <action android:name="android.service.wallpaper.WallpaperService"/>
            </intent-filter>
            <meta-data android:name="android.service.wallpaper" 
                android:resource="@xml/wallpaper"/>
        </service>
         <activity
            android:name=".LiveWallpaperSettings"
            android:exported="true"
            android:icon="@drawable/logo"
            android:label="@string/live_wallpaper_settings"
            android:theme="@android:style/Theme.Black" >
        </activity>
    </application>

</manifest>
Now we are going to create a class for the available selections. Create a file LiveWallpaperPreferences.java
Add this code:
package com.ceecee.android.live;


import android.content.Context;
import android.content.SharedPreferences;


public class LiveWallpaperPreferences {

 // Handle for the LiveWallpaperPreferences singleton instance
 private static LiveWallpaperPreferences INSTANCE;
 
 // String containing the live wallpaper's preferences name
 private static final String PREFERENCE_NAME = "LWP_PREFS";
 
 // String containing the key to the background preference value
 private static final String BACKGROUND_KEY = "BACKGROUND_COLOR";
 private static final String DEFAULT_BACKGROUND = "Black";
 
 // Shared preference objects
 private SharedPreferences mSharedPreferences;
 private SharedPreferences.Editor mSharedPreferencesEditor;
 
 // Shared preference values
 private String mBackground;
 
 LiveWallpaperPreferences(){
  // Do nothing...
 }
 
 // Obtain the LiveWallpaperPreferences instance
 public static LiveWallpaperPreferences getInstance(){
  if(INSTANCE == null){
   INSTANCE = new LiveWallpaperPreferences();
  }
  return INSTANCE;
 }
 
 // Initialize the wallpaper's preference file
 public void initPreferences(Context pContext){
  if(mSharedPreferences == null){
   mSharedPreferences = pContext.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
   mSharedPreferencesEditor = mSharedPreferences.edit();
   
   mBackground = mSharedPreferences.getString(BACKGROUND_KEY, DEFAULT_BACKGROUND);
  }
 }
 
 // Return the saved value for the mBackground variable
 
 public String getBackground(){
  return mBackground;
 }
 
 public void setBackground(String pBackground){
  this.mBackground = pBackground;
  this.mSharedPreferencesEditor.putString(BACKGROUND_KEY, mBackground);
  this.mSharedPreferencesEditor.commit();
 }
}
We also need to create the file LiveWallpaperSettings.java. This activity responds to the user changes in the radio buttons.
package com.ceecee.android.live;

import android.app.Activity;
import android.os.Bundle;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;

 public class LiveWallpaperSettings extends Activity implements OnCheckedChangeListener{
 
  private RadioGroup radioBackgroundGroup;
  private RadioButton radioBackgroundButton;
  private String mBackground;

  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   this.setContentView(R.layout.prefs);   
   radioBackgroundGroup = (RadioGroup) findViewById(R.id.radioBackground); 
   radioBackgroundGroup.setOnCheckedChangeListener(this); 
   
    }

  @Override
  protected void onPause() {
   // onPause(), we save the current value to the preference file.
   LiveWallpaperPreferences.getInstance().setBackground(mBackground);

   super.onPause();
  }

  @Override
  public void onCheckedChanged(RadioGroup group, int selectedId) {
   // TODO Auto-generated method stub

  selectedId = radioBackgroundGroup.getCheckedRadioButtonId();
  radioBackgroundButton = (RadioButton) findViewById(selectedId);
  mBackground = (String) radioBackgroundButton.getText();
 
  }

 }
  
Finally we need to edit the LiveWallpaperService.java file as follows. Add the variable
private String mBackground;
Now add the following code to set the background to the proper color.
@Override
 public void onCreateScene(OnCreateSceneCallback createSceneCallback) throws Exception {  
  mScene= new Scene();

//add the background to the scene 
//background color is added based on user selected radio button
  mBackground = LiveWallpaperPreferences.getInstance().getBackground();
  if(mBackground.equals("Black"))
   mScene.setBackground(new Background(0.0f, 0.0f, 0.0f)); 
  else if(mBackground.equals("Green"))
   mScene.setBackground(new Background(0.0f, 0.9f, 0.0f));
  else if(mBackground.equals("Blue")) 
   mScene.setBackground(new Background(0.0f, 0.0f, 0.9f));
Also we will need to alter the code that is executed onResumeGame
/ enable sensors when resumed
 @Override
    public void onResumeGame() {
  mBackground = LiveWallpaperPreferences.getInstance().getBackground();
  if(mBackground.equals("Black"))
   mScene.setBackground(new Background(0.0f, 0.0f, 0.0f)); 
  else if(mBackground.equals("Green"))
   mScene.setBackground(new Background(0.0f, 0.9f, 0.0f));
  else if(mBackground.equals("Blue")) 
   mScene.setBackground(new Background(0.0f, 0.0f, 0.9f));
   
         super.onResumeGame();
         this.enableAccelerationSensor(this);
    }
       

Sunday, February 17, 2013

LiveWallpaper updates to improve performance

I have been reading over some sample code in andengine's posts. I decided to incorporate some ideas here so as to save on battery life. I would like to thank efung and his Glimmer Live Wallpaper (with source!). It can be found at https://github.com/efung/glimmer. I encourage you all to check it out. Also I have been reading "AndEngine for Android Game Development Cookbook" by Jayme Schroeder, Brian Broyles. This book contains thorough explanations and lots of sample code recipes. I recommend it highly.

First of all when writing wallpaper it is best to set its priority to 1. The value should be an integer with higher integers given higher priority. You want your wallpaper to have low priority. This is set in the AndroidManifest.xml file. Here is the snippet with the change.
<intent-filter android:priority="1">
   <action android:name="android.service.wallpaper.WallpaperService"/>
</intent-filter>
There are several other modifications that need to be made to keep a livewallpaper from draining the battery. Secondly I changed ParticleSystem to BatchedSpriteParticleSystem. From what I understand this will increase performance without compromising on the battery life. You need to change the declaration of course.
final BatchedSpriteParticleSystem particleSystem = 
      new BatchedSpriteParticleSystem(new PointParticleEmitter
      (mParticleX, mParticleY), mParticleMinRate, mParticleMaxRate,
       mParticleMax, this.mFlowerTextureRegion, 
       this.getVertexBufferObjectManager());
Then you need to replace every instance of <Sprite> with <UncoloredSprite> like this:
particleSystem.addParticleInitializer(new BlendFunctionParticleInitializer
<UncoloredSprite>(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE));
The third change I made was to disable the accelerometer when the game was paused. Then of course onResume you will need to enable it.

@Override
public void onResumeGame() {
       super.onResumeGame();
       this.enableAccelerationSensor(this);
       }
             
protected boolean disableAccelerationSensor() {
        return this.mEngine.disableAccelerationSensor(this);
        }
@Override
public void onPauseGame() {
        super.onPauseGame();
        this.disableAccelerationSensor();
        }
I have committed all changes to github at https://github.com/RealWorldApplications/LiveWallpaperExample.

Thursday, January 31, 2013

Adding fade effect to live wallpaper

On the andengine forum I received a request to add a fade in and fade out effect to the falling rose petals. We will use the AlphaParticleModifier to accomplish this. The first two values of the modifier give the start and end values in seconds for how long the effect will take place. The second two values are the start and end of the alpha effect with a range of 0.0 to 1.0. The alpha effect refers to the transparency of the particle with 0.0 being invisible.
// add some fade in and fade out to the particles
    particleSystem.addParticleModifier(new AlphaParticleModifier<Sprite>
  (0.0f,10.f,0.0f, 1.0f));
    particleSystem.addParticleModifier(new AlphaParticleModifier<Sprite>
  (25.0f, 40.0f, 1.0f, 0.0f));
I also received a request on how to animate the sprites. I will make that the topic of the next blog. It will be posted before the end of the week. So check back shortly. Thanks for reading.

Monday, January 28, 2013

Creating Live Wallpaper with AndEngine


This tutorial covers creating a live wallpaper using andengine. It assumes you have eclipse, android sdk, and andengine installed. All of the source code for this project is available on github at https://github.com/RealWorldApplications/LiveWallpaperExample.

Here is a quick break down of the steps involved in this project.
1. Create a new project.
2. Edit the Android Manifest and create the file wallpaper.xml.
3. Add images to the assets folder.
4. Code the live wallpaper source file.
5. Create an AVD to run the application in the emulator.

First in eclipse create a new android application project. 
File-> New Project -> Android -> Android Application Project Next>

Add the following name and be sure that the minimum required SDK is at least 2.1

Click Next. You do not need to add a launcher icon instead we will add a logo. Remember to uncheck the box for creating an Activity now click finish.

Next we need to add the andengine libraries to the project. Right click on the project and choose Properties. In the window that pops up choose Android. Now add Andengine and AndengineLiveWallpaperExtension libraries to your project.
 

Now that you have your project created, the Android manifest file needs to be edited. Open the AndroidManifest.xml and edit the file to incorporate the following changes. You need to define the service called LiveWallpaperService. Also add the uses-feature to define the livewallpaper.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ceecee.android.live"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />
    <uses-feature  
        android:glEsVersion="0x00020000"
        android:required="true"
        android:name="android.software.live_wallpaper">
    </uses-feature>

    <application
        android:icon="@drawable/logo"
        android:label="@string/app_name" >
        <!--  Live Wallpaper Service -->
        <service android:name="com.ceecee.android.live.LiveWallpaperService"
            android:enabled="true"
            android:permission="android.permission.BIND_WALLPAPER" 
            android:icon="@drawable/logo" 
            android:description="@string/wallpaper_description">
            <intent-filter>
                <action android:name="android.service.wallpaper.WallpaperService"/>
            </intent-filter>
            <meta-data android:name="android.service.wallpaper" 
                android:resource="@xml/wallpaper"/>
        </service>
    </application>

</manifest>

Now create a new folder /res/xml. Create a file in that folder called wallpaper.xml. Add the following code to that file.

<?xml version="1.0" encoding="UTF-8"?>
<wallpaper
        xmlns:android="http://schemas.android.com/apk/res/android"  
        android:thumbnail="@drawable/logo"
        android:description="@string/wallpaper_description"
        />





Edit the strings.xml file that is located in the values folder.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Falling Petals LiveWallpaper</string>
    <string name="wallpaper_description">
         <Rose Petals fall from top of screen</string>
</resources>
Now add any images to the assets/gfx folder.  For this exercise you can just copy mine. 
 Ok now to get on to the coding. In the source folder create a file called LiveWallpaperService.java.
I have added extensive comments to this file which should explain all the code.

package com.ceecee.android.live;
package com.ceecee.android.live;


import org.andengine.engine.camera.Camera;
import org.andengine.engine.options.EngineOptions;
import org.andengine.engine.options.ScreenOrientation;
import org.andengine.engine.options.resolutionpolicy.RatioResolutionPolicy;

import org.andengine.entity.particle.SpriteParticleSystem;
import org.andengine.entity.particle.emitter.PointParticleEmitter;
import org.andengine.entity.particle.initializer.AccelerationParticleInitializer;
import org.andengine.entity.particle.initializer.RotationParticleInitializer;
import org.andengine.entity.particle.initializer.VelocityParticleInitializer;
import org.andengine.entity.particle.initializer.GravityParticleInitializer;
import org.andengine.entity.particle.modifier.ExpireParticleInitializer;
import org.andengine.entity.particle.modifier.RotationParticleModifier;
import org.andengine.entity.scene.Scene;
import org.andengine.entity.scene.background.Background;
import org.andengine.entity.sprite.Sprite;
import org.andengine.extension.ui.livewallpaper.BaseLiveWallpaperService;
import org.andengine.input.sensor.acceleration.AccelerationData;
import org.andengine.input.sensor.acceleration.IAccelerationListener;

import org.andengine.opengl.texture.TextureOptions;
import org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas;
import org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlasTextureRegionFactory;
import org.andengine.opengl.texture.region.ITextureRegion;

import android.util.DisplayMetrics;
import android.view.WindowManager;

public class LiveWallpaperService extends BaseLiveWallpaperService
 implements IAccelerationListener{

//================================================================================
//                                  Fields
//================================================================================

private static int CAMERA_WIDTH = 480;
private static int CAMERA_HEIGHT = 720;
 
private Camera mCamera;
private Scene mScene;

private ITextureRegion mFlowerTextureRegion; 
private BitmapTextureAtlas mFlowerTexture; 
private VelocityParticleInitializer<Sprite> mVelocityParticleInitializer;
 
@Override
public EngineOptions onCreateEngineOptions() {
  
final DisplayMetrics displayMetrics = new DisplayMetrics();
 WindowManager wm = (WindowManager)getSystemService(WINDOW_SERVICE);
 wm.getDefaultDisplay().getMetrics(displayMetrics);
 wm.getDefaultDisplay().getRotation();
 CAMERA_WIDTH = displayMetrics.widthPixels;
 CAMERA_HEIGHT = displayMetrics.heightPixels;
 this.mCamera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT);
  
 return new EngineOptions(true, ScreenOrientation.PORTRAIT_FIXED,
 new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT), this.mCamera);
 }

@Override
public void onCreateResources
  (OnCreateResourcesCallback createResourcesCallback)throws Exception {
   this.mFlowerTexture = new BitmapTextureAtlas
  (this.getTextureManager(),64,64, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
      this.mFlowerTextureRegion 
BitmapTextureAtlasTextureRegionFactory.createFromAsset  
(this.mFlowerTexture, this, "gfx/rosetrans64.png",0,0);      
  this.getEngine().getTextureManager().loadTexture(this.mFlowerTexture);
  this.enableAccelerationSensor(this);
   createResourcesCallback.onCreateResourcesFinished();
}


@Override
public void onCreateScene(OnCreateSceneCallback createSceneCallback) throws Exception {  
 mScene= new Scene();

//add the background to the scene 
// I chose a black background to accentuate the red rose color
 mScene.setBackground(new Background(0.0f, 0.0f, 0.0f)); 
  
// set the x y values of where the petals fall from
 final int mParticleX = CAMERA_WIDTH/2;
 final int mParticleY = 0;
//Set the max and min rates that particles are generated per second
 final int mParticleMinRate = 1;
 final int mParticleMaxRate = 2;
//Set a variable for the max particles in the system.
 final int mParticleMax = 40;
  
/* Create Particle System. */ 
final SpriteParticleSystem particleSystem = new SpriteParticleSystem
 (new PointParticleEmitter(mParticleX, mParticleY), 
 mParticleMinRate, mParticleMaxRate, mParticleMax,
 this.mFlowerTextureRegion, this.getVertexBufferObjectManager());
      
//set initial velocity  
this.mVelocityParticleInitializer =
   new VelocityParticleInitializer<Sprite>(-100, 100, 20, 190);
particleSystem.addParticleInitializer(this.mVelocityParticleInitializer);
   
//add gravity so the particles fall downward
particleSystem.addParticleInitializer
   (new GravityParticleInitializer<Sprite>());
//add acceleration so particles float 
particleSystem.addParticleInitializer
  (new AccelerationParticleInitializer<Sprite>(0, -10));
//add a rotation to particles
particleSystem.addParticleInitializer
   (new RotationParticleInitializer<Sprite>(0.0f, 90.0f));
//have particles expire after 20
particleSystem.addParticleInitializer
  (new ExpireParticleInitializer<Sprite>(40.0f));

//change rotation of particles at various times
particleSystem.addParticleModifier(new RotationParticleModifier<Sprite>
(0.0f, 10.0f, 0.0f, -180.0f));
particleSystem.addParticleModifier(new RotationParticleModifier<Sprite>
(10.0f, 20.0f, -180.0f, 90.0f));
particleSystem.addParticleModifier(new RotationParticleModifier<Sprite>
(20.0f, 30.0f, 90.0f, 0.0f));
particleSystem.addParticleModifier(new RotationParticleModifier<Sprite>
(30.0f, 40.0f, 0.0f, -90.0f));

  
//attach particle system to scene
this.mScene.attachChild(particleSystem);
  
createSceneCallback.onCreateSceneFinished(mScene);
 
}  

@Override
public void onPopulateScene(Scene arg0, 
OnPopulateSceneCallback populateSceneCallback)throws Exception {
 populateSceneCallback.onPopulateSceneFinished();
}

@Override
public void onAccelerationAccuracyChanged
(AccelerationData pAccelerationData) {
// TODO Auto-generated method stub  
}

// Change the petals to move along the axes of the accelerometer
@Override
public void onAccelerationChanged(AccelerationData pAccelerationData) {
 final float minVelocityX = (pAccelerationData.getX() + 2) * 2;
 final float maxVelocityX = (pAccelerationData.getX() - 2) * 2; 
 final float minVelocityY = (pAccelerationData.getY() - 4) * 5;
 final float maxVelocityY = (pAccelerationData.getY() - 6) * 5;
 this.mVelocityParticleInitializer.setVelocity(minVelocityX, 
maxVelocityX, minVelocityY, maxVelocityY); 
}
}


Now create an AVD with the hardware acceleration enable.

Run your project!