I’ve recently been looking into creating my first Live Wallpaper for Android as an opportunity to learn more about OpenGL ES and hopefully create something neat at the same time. Throughout our entire series of Android tutorials, we’ve been using GLSurfaceView to take care of the hard work of threading and initialization for us; imagine my surprise when I learned that there is no equivalent for live wallpapers! What we’re going to cover in this lesson
If you want to do OpenGL in a live wallpaper, it seems that there are a few alternatives:
Since this is a research article, we’ll take a look at both options two and three. To start off, we’ll first look at adapting the existing GLSurfaceView for use within a live wallpaper. Later, we’ll see how we can use Robert Green’s work for rendering with OpenGL ES 2.0. Here are the two main approaches we’re going to look at:
We’ll also look at combining the two approaches and allowing the user to select between the two. Let’s begin with the first approach: using GLSurfaceView. Approach One: Using GLSurfaceView inside a live wallpaperThe first evidence I found that it was possible to use GLSurfaceView in a live wallpaper was from these files, by Ben Gruver:
I found these through this stack overflow question: http:///questions/4998533/android-live-wallpapers-with-opengl-es-2-0 Full credits to Ben Gruver for showing us one way that it could be done; in this article, we’ll work with our own adaptation of GLSurfaceView, with an emphasis on using it for OpenGL ES 2.0. 1. Creating the wallpaper serviceAndroid Live Wallpapers are built off of WallpaperService, so the first thing we’ll do is create a new class called GLWallpaperService. Each service is really just a wrapper around an engine, which is subclassed from WallpaperService.Engine. This engine handles the actual lifecycle events of the live wallpaper. We’ll be working with the Android OpenGL tutorials as a base, which can be downloaded from GitHub.
Creating a new wallpaper service subclassAt the beginning of our new class, GLWallpaperService, extend WallpaperService:
Creating a new wallpaper engine subclassWe’ll then create a new engine, so let’s also extend WallpaperService.Engine as an inner class:
Creating a custom subclass of GLSurfaceViewInside the engine, create a new subclass of GLSurfaceView, called WallpaperGLSurfaceView:
We’ll be using this special subclass of GLSurfaceView to handle initializing OpenGL. The two methods to take note of are getHolder() and onDestroy(). Let’s take a look at these two methods in more detail:
Now that we have our extended GLSurfaceView in place, we can add a few lifecycle events and helper methods to round out a basic implementation: Adding lifecycle events to the engine
We initialize glSurfaceView in onCreate(). Remember that these methods should be placed inside of our inner class, GLEngine, and not at the same level as the wallpaper service. Let’s add a lifecycle event to handle visibility:
Since we don’t have activity onPause() and onResume() events, we hook into the wallpaper engine’s onVisibilityChanged() event, instead. If the live wallpaper’s visible, we tell glSurfaceView to resume, and if it’s no longer visible, we tell it to pause. We do check if the glSurfaceView is ready, since we shouldn’t call these methods unless a renderer has been set. Let’s round out the life cycle events:
When the live wallpaper is destroyed, we tell glSurfaceView to stop rendering, using the custom onDestroy() method we defined earlier. Adding helper methods to the engineThere’s also a couple of helper methods we’ll want to call from subclasses. Let’s define them now:
These methods simply wrap the glSurfaceView, so that we can call them from our subclasses. 2. Initializing OpenGL ES 2.0The next step is to create a subclass that is derived from the GLWallpaperService that we’ve just created; this subclass will initialize OpenGL ES 2.0 and also initialize a custom renderer. Create a new class, OpenGLES2WallpaperService, and add the following code:
As you can see, this is very similar to the OpenGL ES 2.0 initialization code that we’ve added to all of our activities in the Android tutorials. Calling setPreserveEGLContextOnPause() will ask the GLSurfaceView to preserve the OpenGL context, which will speed up pausing and restarting the live wallpaper by holding onto the surface. 3. Initializing a custom rendererTo round out the Java code, let’s add a final subclass, LessonThreeWallpaperService, which is going to use the renderer from lesson three as our live wallpaper:
4. Adding the wallpaper to the manifestBefore we can install the live wallpaper on a device, we need to add it to the manifest and create a configuration file for it. Add the following to AndroidManifest.xml: <service android:name=".livewallpaper.LessonThreeWallpaperService" android:label="@string/lesson_three_wallpaper_1" android:permission="android.permission.BIND_WALLPAPER" > <intent-filter> <action android:name="android.service.wallpaper.WallpaperService" /> </intent-filter> <meta-data android:name="android.service.wallpaper" android:resource="@xml/wallpaper" /> </service> This tells Android that this service is a live wallpaper, with the label set to @string/lesson_three_wallpaper_1. Let’s define a couple extra strings in strings.xml as follows: <string name="lesson_three_wallpaper_1">Using GLSurfaceView</string> <string name="lesson_three_wallpaper_2">Using re-implementation of GLSurfaceView</string> <string name="lesson_three_wallpaper_3">Switching implementations</string> We also need to create a wallpaper definition file. Create a new file, /res/xml/wallpaper.xml, and add the following: <wallpaper xmlns:android="http://schemas./apk/res/android" android:thumbnail="@drawable/ic_lesson_three" /> This tells Android that this wallpaper should use this thumbnail when the user looks at the list of live wallpapers. 5. Build the application and view the wallpaper.Now we can build the app and check out the live wallpaper. Install the app to your device, then go to your live wallpapers. You should see the following: After selecting the live wallpaper, you should see a preview of lesson 3 on the screen: Caveats with GLSurfaceViewHere are some of the caveats I can think of off of the top of my head; since this is a research article, I’d love to get your feedback.
Approach Two: Using a custom live wallpaper based on the internals of GLSurfaceView[Note: The wallpaper has changed since this was first written, as Mark Guerra and other contributors have extended Robert Green's work at this GitHub repository: https://github.com/GLWallpaperService/GLWallpaperService. The rest of this section is no longer required for adding support for OpenGL ES 2.0.] As the second part of this research article, we’ll also be creating a live wallpaper with Robert Green and Mark Guerra’s adaptation of the code from GLSurfaceView. For this, you’ll want to download GLWallpaperService from http:///gbhjmQ. Since we already have a GLWallpaperService, let’s create a new package to contain this class, and let’s call it com.learnopengles.android.rbgrnlivewallpaper. After you have copied the class from GitHub, be sure to update the package name and any imports. 1. Adding support for OpenGL ES 2.0.The first thing to note with this wallpaper service is that it doesn’t seem to have support for OpenGL ES 2.0. We’ll add this support by using the source code for GLSurfaceView as a base. Open up the GLWallPaperService we just downloaded, and let’s make the following changes: Adding mEGLContextClientVersionThe first thing we’ll do is add a new variable called mEGLContextClientVersion. Add the following to the beginning of GLEngine:
Adding a method to set the EGL context client versionThe next thing we’ll need to do is adapt setEGLContextClientVersion() from GLSurfaceView. Add the following method before setRenderer():
Updating DefaultContextFactoryThe next thing we need to do is to update createContext() inside the class DefaultContextFactory. Update the interface EGLContextFactory and the class DefaultContextFactory as follows:
Updating EglHelperWe’ll also need to fix the call to createContext(). Find the call to mEGLContextFactory.createContext() which is located inside the class EglHelper, and update it as follows:
Since mEGLContextClientVersion has not been defined in this scope, add it to the beginning of EglHelper, and pass it in the constructor as follows:
Updating GLThreadNow GLThread doesn’t compile, so we’ll need to define mEGLContextClientVersion in that scope, too. Add the following code:
Now we can update the call to new EglHelper as follows:
Updating setRenderer()Now that we’ve added the variables at the right scope levels, we can now adjust the call to new GLThread, and pass in the mEGLContextClientVersion from GLEngine. Update the call to new GLThread in setRenderer() as follows:
Updating BaseConfigChooserThere’s one more change we need to do before we can use setEGLContextClientVersion(). Update the class BaseConfigChooser as follows:
Updating ComponentSizeChooserWe’ll need to scope this new variable, so let’s update the constructor for ComponentSizeChooser:
Updating SimpleEGLConfigChooserWe’ll need to keep scoping this variable in, so update SimpleEGLConfigChooser as follows:
Updating methods in GLEngineNow that we’ve added the scoping, we’ll have to update our calls from our methods in GLEngine as follows:
2. Initializing OpenGL ES 2.0.Now that we’ve updated GLWallpaperView to add support for OpenGL ES 2.0, we can now subclass it to initialize OpenGL ES 2.0. Let’s copy the same OpenGLES2WallpaperService from before into our new package, com.learnopengles.android.rbgrnlivewallpaper:
3. Adding a renderer.We can also copy LessonThreeWallpaperService from before, and use it as is:
We have a compile error because GLWallpaperService doesn’t use the same renderer interface. Let’s go back to it and delete the following lines:
We’ll need to update some class references, so replace all instances of GLWallpaperService.Renderer with Renderer, and optimize imports. Our code should now compile. 4. Updating the manifest.As before, we’ll need to update the manifest to add in the new live wallpaper. Add the following to AndroidManifest.xml: <service android:name=".rbgrnlivewallpaper.LessonThreeWallpaperService" android:label="@string/lesson_three_wallpaper_2" android:permission="android.permission.BIND_WALLPAPER" > <intent-filter> <action android:name="android.service.wallpaper.WallpaperService" /> </intent-filter> <meta-data android:name="android.service.wallpaper" android:resource="@xml/wallpaper" /> </service> 5. Build the application and view the wallpaper.If everything went well, then you should now see two live wallpapers in the list, corresponding to the two that we’ve created: When we select one of the wallpapers, we should see the following: Taking the best of approaches one & two: switching between implementations in a single live wallpaperWe’ve now shown that we can use either implementation as a backing for our live wallpaper: either by using a GLSurfaceView directly with a few slight modifications, or by adapting Robert Green’s work (itself based off of the internals of GLSurfaceView) to support OpenGL ES 2.0. What if we could switch between implementations, based on a user toggle? That could be a useful debugging feature: if ever a user has an issue with one implementation, they could always try the other. To do this, we need a new service that will return either the first engine we created or the second, based on the configured setting. Let’s create a new package called com.learnopengles.android.switchinglivewallpaper, and let’s create a new class called, you guessed it, GLWallpaperService. 1. Updating GLWallpaperService to support switchingThe first thing we’ll do is copy the entire class body of GLWallpaperService from com.learnopengles.android.livewallpaper into the new GLWallpaperService. The only difference is that we’ll rename the engine to GLSurfaceViewEngine. The second thing we’ll do is copy the entire class body of GLWallpaperService from com.learnopengles.android.rbgrnlivewallpaper into the new GLWallpaperService; let’s call that engine RbgrnGLEngine. We can update each engine to implement the following interface, so that we won’t have to duplicate code afterwards:
The code is too long to paste in its entirety, so you can double check against the GitHub code here: https://github.com/learnopengles/Learn-OpenGLES-Tutorials/blob/master/android/AndroidOpenGLESLessons/src/com/learnopengles/android/switchinglivewallpaper/GLWallpaperService.java 2. Creating a new OpenGLES2WallpaperService to support switchingIn com.learnopengles.android.switchinglivewallpaper, create a new OpenGLES2WallpaperService with the following code:
As you can see, this is similar to the previous versions, except that it’s been updated to support switching. We can copy LessonThreeWallpaperService over to the new package as-is. 3. Adding a settings activityThe next thing we want to do is add a settings activity. Create WallpaperSettings as follows:
Creating the settings XMLWe also need to create an XML for the settings. Create preferences.xml under /res/xml: <?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas./apk/res/android" > <CheckBoxPreference android:key="use_gl_surface_view" android:title="Use GLSurfaceView" android:defaultValue="true"/> </PreferenceScreen> Creating a new wallpaper XMLWe’ll also need to create a new wallpaper.xml. Copy the existing wallpaper.xml to switching_wallpaper.xml, and update its content as follows: <?xml version="1.0" encoding="utf-8"?> <wallpaper xmlns:android="http://schemas./apk/res/android" android:settingsActivity="com.learnopengles.android.switchinglivewallpaper.WallpaperSettings" android:thumbnail="@drawable/ic_lesson_three" /> Updating the manifestWe’ll now need to update AndroidManifest.xml. Add the following: <service android:name=".switchinglivewallpaper.LessonThreeWallpaperService" android:label="@string/lesson_three_wallpaper_3" android:permission="android.permission.BIND_WALLPAPER" > <intent-filter> <action android:name="android.service.wallpaper.WallpaperService" /> </intent-filter> <meta-data android:name="android.service.wallpaper" android:resource="@xml/switching_wallpaper" /> </service> <activity android:name=".switchinglivewallpaper.WallpaperSettings" android:exported="true" android:label="@string/lesson_three_wallpaper_3" android:permission="android.permission.BIND_WALLPAPER" android:theme="@style/WallpaperSettingsLight" > </activity> Adding new themesWe defined a new theme, so let’s add the following style definitions: /res/values/styles.xml /res/values-v11/styles.xml /res/values-v14/styles.xml These styles are adapted from the Theme.WallpaperSettings and Theme.Light.WallpaperSettings included with Android, but also updated so that they use the Holo theme on later versions of Android. If you’re having trouble building the project after adding the new styles, change your build target to a newer version of Android (note that the app will still work on older versions, as they will simply ignore the XML). 4. Viewing the new live wallpaperWe can now build and run the application, and we’ll see our new live wallpaper in the list: If we select the new one, “Switching implementations”, and open up the settings, we’ll see something like this: We can use the toggle to change implementations; the toggle won’t take effect until we go back to the list of live wallpapers and then go back in to view the preview. Wrapping upI’d love to hear from those who have actually implemented live wallpapers, as well as from Robert Green and the Android guys if they ever come across this post! I just got this working with a few hours of research and development, but when it comes to this, I have nowhere near the same level of experience as some of the experts out there. I’d love to hear from you about the upsides and downsides of both using GLSurfaceView and of rolling out a custom derivative implementation based on the internals of GLSurfaceView. Further exercisesIs there a better way to handle switching between implementations? How would you do it? Also, what would you do to continue animating the wallpaper even when the settings screen is displayed on top? Maybe when the settings screen appears you can grab a reference to the wallpaper and manually call its onVisibilityChanged()? You could also check for isPreview() directly in onVisibilityChanged(), but that will lead to problems when you exit the preview window. I defined the settings key string in two places (once as a constant in Java, and once as an XML string); what would you do to avoid this duplication? Get the sourceThe full source code for this lesson can be downloaded from the project site on GitHub. A compiled version of the lesson can also be downloaded directly from the Android Market: Thanks for stopping by, and please feel free to check out the code and share your comments below. |
|