Wednesday, October 21, 2015

android - How to create a Live Wallpaper

ColoredLiveWallPaperService.java

package com.cfsuman.me.coloredlivewallpaper;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.service.wallpaper.WallpaperService;
import android.view.SurfaceHolder;
import android.view.MotionEvent;

/*
    A wallpaper service is responsible for showing a live wallpaper behind applications that would
    like to sit on top of it. This service object itself does very little -- its only purpose is to
    generate instances of WallpaperService.Engine as needed. Implementing a wallpaper thus involves
    subclassing from this, subclassing an Engine implementation, and implementing onCreateEngine()
    to return a new instance of your engine.

        Public Methods
            onBind(Intent intent)
            onCreate()
            onCreateEngine()
            onDestroy()
*/
public class ColoredLiveWallPaperService extends WallpaperService {
    /*
        Handler
            A Handler allows you to send and process Message and Runnable objects associated with a
            thread's MessageQueue. Each Handler instance is associated with a single thread and that
            thread's message queue. When you create a new Handler, it is bound to the thread / message
            queue of the thread that is creating it -- from that point on, it will deliver messages
            and runnables to that message queue and execute them as they come out of the message queue.

        public Handler ()
            Default constructor associates this handler with the Looper for the current thread. If
            this thread does not have a looper, this handler won't be able to receive messages so
            an exception is thrown.
    */
    private Handler mHandler = new Handler();

    // Specify the each color box width and height
    private int mRectWidth=50; // Color box width in pixels
    private int mRectHeight=50;  // Color box height in pixels

    // To generate the HSV color
    int hue;
    float saturation;
    float black;

    // Variable to generate a continuous color animation
    int nextStartingHue;

    /*
        public void onCreate ()
            Called by the system when the service is first created. Do not call this method directly.
    */
    @Override
    public void onCreate(){
        super.onCreate();
    }

    /*
        public void onDestroy ()
            Called by the system to notify a Service that it is no longer used and is being removed.
            The service should clean up any resources it holds (threads, registered receivers, etc)
            at this point. Upon return, there will be no more calls in to this Service object and it
            is effectively dead. Do not call this method directly.
    */
    @Override
    public void onDestroy(){
        super.onDestroy();
    }

    /*
        public abstract WallpaperService.Engine onCreateEngine ()
            Must be implemented to return a new instance of the wallpaper's engine. Note that
            multiple instances may be active at the same time, such as when the wallpaper is
            currently set as the active wallpaper and the user is in the wallpaper picker
            viewing a preview of it as well.
    */
    @Override
    public Engine onCreateEngine(){
        return new ColoredLiveWallpaperEngine();
    }

    /*
        WallpaperService.Engine
            The actual implementation of a wallpaper. A wallpaper service may have multiple
            instances running (for example as a real wallpaper and as a preview), each of which
            is represented by its own Engine instance. You must implement onCreateEngine() to
            return your concrete Engine implementation.

            Some public methods
                getSurfaceHolder()
                isPreview()
                isVisible()
                onCommand(String action, int x, int y, int z, Bundle extras, boolean resultRequested)
                onCreate(SurfaceHolder surfaceHolder)
                onDestroy()
                onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep,
                    int xPixelOffset, int yPixelOffset)
                onSurfaceCreated(SurfaceHolder holder)
                onSurfaceChanged(SurfaceHolder holder, int format, int width, int height)
                onTouchEvent(MotionEvent event)
                onVisibilityChanged(boolean visible)
                setTouchEventsEnabled(boolean enabled)
    */
    // Class to create an Engine
    private class ColoredLiveWallpaperEngine extends Engine{
        private boolean mVisible = true;

        /*
            Runnable
                Represents a command that can be executed. Often used to run code in a different Thread.
        */
        // It need to be remove from call back
        private Runnable mDrawRunner = new Runnable() {
            /*
                public abstract void run ()
                    Starts executing the active part of the class' code. This method is called when
                    a thread is started that has been created with a class which implements Runnable.
            */
            @Override
            public void run() {
                drawColoPallet();
            }
        };

        public ColoredLiveWallpaperEngine(){
            // Specify the initial value of HSV color components
            hue = 0;
            saturation = 0.5f;
            black = 1.0f;

            // Define the initial hue for next canvas drawing
            nextStartingHue = 0;
        }

        /*
            public void onCreate (SurfaceHolder surfaceHolder)
                Called once to initialize the engine. After returning, the engine's surface will be
                created by the framework.
        */
        @Override
        public void onCreate(SurfaceHolder surfaceHolder) {
            super.onCreate(surfaceHolder);
        }

        /*
            public void onDestroy ()
                Called right before the engine is going away. After this the surface will be
                destroyed and this Engine object is no longer valid.
        */
        @Override
        public void onDestroy() {
            super.onDestroy();
            mHandler.removeCallbacks(mDrawRunner);
        }

        /*
            public void onVisibilityChanged (boolean visible)
                Called to inform you of the wallpaper becoming visible or hidden. It is very
                important that a wallpaper only use CPU while it is visible..
        */
        @Override
        public void onVisibilityChanged(boolean visible){
            mVisible = visible;
            if (visible) {
                // If the wallpaper is visible the start drawing on the canvas
                drawColoPallet();
            } else {
                /*
                    public final void removeCallbacks (Runnable r)
                        Remove any pending posts of Runnable r that are in the message queue.
                */
                // If the wallpaper is not visible then stop drawing on the canvas
                mHandler.removeCallbacks(mDrawRunner);
            }
        }

        /*
            public void onSurfaceDestroyed (SurfaceHolder holder)
                Convenience for SurfaceHolder.Callback.surfaceDestroyed().

            public abstract void surfaceDestroyed (SurfaceHolder holder)
                This is called immediately before a surface is being destroyed. After returning from
                this call, you should no longer try to access this surface. If you have a rendering
                thread that directly accesses the surface, you must ensure that thread is no longer
                touching the Surface before returning from this function.

                Parameters
                    holder : The SurfaceHolder whose surface is being destroyed.
        */
        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder){
            super.onSurfaceDestroyed(holder);
            mVisible = false;
            mHandler.removeCallbacks(mDrawRunner);
        }

        @Override
        public void onOffsetsChanged(float xOffset, float yOffset, float xStep,
                                     float yStep, int xPixels, int yPixels) {
        }

        /*
            public void onSurfaceChanged (SurfaceHolder holder, int format, int width, int height)
                Convenience for SurfaceHolder.Callback.surfaceChanged().

            public abstract void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
                This is called immediately after any structural changes (format or size) have been
                made to the surface. You should at this point update the imagery in the surface.
                This method is always called at least once, after surfaceCreated(SurfaceHolder).
        */
        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height){
            super.onSurfaceChanged(holder, format, width, height);
        }

        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
            super.onSurfaceCreated(holder);
        }

        /*
            public void onTouchEvent (MotionEvent event)
                Called as the user performs touch-screen interaction with the window that is currently
                showing this wallpaper. Note that the events you receive here are driven by the
                actual application the user is interacting with, so if it is slow you will get fewer move events.

            MotionEvent
                Object used to report movement (mouse, pen, finger, trackball) events. Motion events
                may hold either absolute or relative movements and other data, depending on the type of device.
        */
        @Override
        public void onTouchEvent(MotionEvent event){
            super.onTouchEvent(event);
        }


        // Custom method to draw a color pallet
        void drawColoPallet(){
            /*
                SurfaceHolder
                    Abstract interface to someone holding a display surface. Allows you to control
                    the surface size and format, edit the pixels in the surface, and monitor changes
                    to the surface. This interface is typically available through the SurfaceView class.

                    When using this interface from a thread other than the one running its SurfaceView,
                    you will want to carefully read the methods lockCanvas() and Callback.surfaceCreated().
            */
            /*
                WallpaperService.Engine
                    public SurfaceHolder getSurfaceHolder ()
                        Provides access to the surface in which this wallpaper is drawn.

            */
            // Get the SurfaceHolder object
            SurfaceHolder holder = getSurfaceHolder();

            /*
                Canvas
                    The Canvas class holds the "draw" calls. To draw something, you need 4 basic
                    components: A Bitmap to hold the pixels, a Canvas to host the draw calls
                    (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap),
                    and a paint (to describe the colors and styles for the drawing).
            */
            /*
                public abstract Canvas lockCanvas ()
                    Start editing the pixels in the surface. The returned Canvas can be used to draw
                    into the surface's bitmap. A null is returned if the surface has not been created
                    or otherwise cannot be edited. You will usually need to implement
                    Callback.surfaceCreated to find out when the Surface is available for use.

                    The content of the Surface is never preserved between unlockCanvas() and lockCanvas(),
                    for this reason, every pixel within the Surface area must be written. The only
                    exception to this rule is when a dirty rectangle is specified, in which case,
                    non-dirty pixels will be preserved.

                    Returns
                        Canvas Use to draw into the surface.
            */
            // Initialize the Canvas to draw something
            Canvas canvas = holder.lockCanvas();

            try{
                // Get the canvas width and height
                int canvasWidth = canvas.getWidth();
                int canvasHeight = canvas.getHeight();

                /*
                    Paint
                        The Paint class holds the style and color information about how to draw
                        geometries, text and bitmaps.
                */
                // Initialize a new Paint instance
                Paint paint = new Paint();
                paint.setStyle(Paint.Style.FILL);

                // Generate the color pallet
                for(int left=0;left<=canvasWidth;left+=mRectWidth){
                    for(int top=0;top<=canvasHeight;top+=mRectHeight){
                        // Generate next HSV color
                        if(hue>=360){
                            hue = 0;
                        }
                        if(saturation >=1.0f){
                            saturation = 0.5f;
                        }
                        hue +=1;
                        saturation +=0.1f;
                        paint.setColor(createHSVColor(hue, saturation,black));

                        if(canvas != null){
                            /*
                                public Rect (int left, int top, int right, int bottom)
                                    Create a new rectangle with the specified coordinates. Note: no
                                    range checking is performed, so the caller must ensure that
                                    left <= right and top <= bottom.
                            */
                            Rect rectangle = new Rect(left, top, left + mRectWidth, top + mRectHeight);
                            /*
                                public void drawRect (Rect r, Paint paint)
                                    Draw the specified Rect using the specified Paint. The rectangle
                                    will be filled or framed based on the Style in the paint.
                            */
                            canvas.drawRect(rectangle, paint);
                        }
                    }
                }

                // Add one with current canvas starting hue
                nextStartingHue +=1;
                // Specify the next canvas starting hue
                hue = nextStartingHue;
            }finally{
                if(canvas != null){
                    /*
                        public abstract void unlockCanvasAndPost (Canvas canvas)
                            Finish editing pixels in the surface. After this call, the surface's current
                            pixels will be shown on the screen, but its content is lost, in particular
                            there is no guarantee that the content of the Surface will remain unchanged
                            when lockCanvas() is called again.

                            Parameters
                                canvas : The Canvas previously returned by lockCanvas().
                    */
                    holder.unlockCanvasAndPost(canvas);
                }
            }

            // Now remove the call backs from handler
            mHandler.removeCallbacks(mDrawRunner);

            // If visible continue the animation
            if(mVisible){
                /*
                    public final boolean postDelayed (Runnable r, long delayMillis)
                        Causes the Runnable r to be added to the message queue, to be run after the
                        specified amount of time elapses. The runnable will be run on the thread to
                        which this handler is attached. The time-base is uptimeMillis(). Time spent
                        in deep sleep will add an additional delay to execution.

                        Returns
                            Returns true if the Runnable was successfully placed in to the message
                            queue. Returns false on failure, usually because the looper processing
                            the message queue is exiting.
                */
                // Wait 500 milliseconds to redraw on the canvas
                //1000 milliseconds = 1 second
                mHandler.postDelayed(mDrawRunner, 1000);
            }
        }

        // Create HSV color from values
        private int createHSVColor(float hue, float saturation, float black){
        /*
            Hue is the variation of color
            Hue range 0 to 360

            Saturation is the depth of color
            Range is 0.0 to 1.0 float value
            1.0 is 100% solid color

            Value/Black is the lightness of color
            Range is 0.0 to 1.0 float value
            1.0 is 100% bright less of a color that means black
        */
            int color = Color.HSVToColor(255, new float[]{hue, saturation, black});
            return color;
        }
    }
}
xml/colored_live_wallpaper.xml

<?xml version="1.0" encoding="utf-8"?>
<wallpaper
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="Colored Live Wallpaper"
    android:thumbnail="@drawable/preview"
    />
AndroidManifest.xml

<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cfsuman.me.coloredlivewallpaper"
    >

    <application
        android:allowBackup="true"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <service
            android:name=".ColoredLiveWallPaperService"
            android:label="Colored Live Wallpaper"
            android:enabled="true"
            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/colored_live_wallpaper"
                >
            </meta-data>
        </service>

    </application>

    <uses-feature
        android:name="android.software.live_wallpaper"
        android:required="true"
        />

</manifest>
More android examples