OSDN Git Service

2295787: Rewrite Nexus wallpaper in RenderScript
authorMike Cleron <mcleron@google.com>
Wed, 2 Dec 2009 10:04:46 +0000 (02:04 -0800)
committerMike Cleron <mcleron@google.com>
Thu, 3 Dec 2009 06:14:30 +0000 (22:14 -0800)
res/drawable-hdpi/glow.png [new file with mode: 0644]
res/drawable-hdpi/pulse.png [new file with mode: 0644]
res/raw/nexus.rs [new file with mode: 0644]
src/com/android/wallpaper/nexus/NexusRS.java [new file with mode: 0644]
src/com/android/wallpaper/nexus/NexusWallpaper.java

diff --git a/res/drawable-hdpi/glow.png b/res/drawable-hdpi/glow.png
new file mode 100644 (file)
index 0000000..613c45b
Binary files /dev/null and b/res/drawable-hdpi/glow.png differ
diff --git a/res/drawable-hdpi/pulse.png b/res/drawable-hdpi/pulse.png
new file mode 100644 (file)
index 0000000..4bdfc5f
Binary files /dev/null and b/res/drawable-hdpi/pulse.png differ
diff --git a/res/raw/nexus.rs b/res/raw/nexus.rs
new file mode 100644 (file)
index 0000000..43dde76
--- /dev/null
@@ -0,0 +1,294 @@
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma version(1)
+#pragma stateVertex(PVOrtho)
+#pragma stateFragment(PFTexture)
+#pragma stateStore(PSSolid)
+
+#define MAX_PULSES           20
+#define MAX_EXTRAS           40
+#define PULSE_SIZE           14 // Size in pixels of a cell
+#define HALF_PULSE_SIZE      7
+#define GLOW_SIZE            32 // Size of the leading glow in pixels
+#define HALF_GLOW_SIZE       16 
+#define SPEED                0.2f // (200 / 1000) Pixels per ms
+#define SPEED_VARIANCE       0.3f
+#define PULSE_NORMAL         0
+#define PULSE_EXTRA          1
+#define TRAIL_SIZE           40 // Number of cells in a trail
+#define MAX_DELAY               2000 // Delay between a pulse going offscreen and restarting
+
+struct pulse_s {
+    int pulseType;
+    float originX;
+    float originY;
+    int color;
+    int startTime;
+    float dx;
+    float dy;
+    int active;
+};
+struct pulse_s gPulses[MAX_PULSES];
+
+struct pulse_s gExtras[MAX_EXTRAS];
+
+int gNow;
+
+
+void setColor(int c) {
+    if (c == 0) {
+        // red
+        color(1.0f, 0.0f, 0.0f, 1.0f);
+    } else if (c == 1) {
+        // green
+        color(0.0f, 0.6f, 0.0f, 1.0f);
+    } else if (c == 2) {
+        // blue
+        color(0.0f, 0.4f, 0.8f, 1.0f);
+    } else if (c == 3) {
+        // yellow
+        color(1.0f, 0.8f, 0.0f, 1.0f);
+    }
+}
+
+void initPulse(struct pulse_s * pulse, int pulseType) {
+    if (randf(1) > 0.5f) {
+        pulse->originX = (int)randf(State->width * 2 / PULSE_SIZE) * PULSE_SIZE;
+        pulse->dx = 0;
+        if (randf(1) > 0.5f) {
+            // Top
+            pulse->originY = 0;
+            pulse->dy = randf2(1.0f - SPEED_VARIANCE, 1.0 + SPEED_VARIANCE);
+        } else {
+            // Bottom
+            pulse->originY = State->height;
+            pulse->dy = -randf2(1.0f - SPEED_VARIANCE, 1.0 + SPEED_VARIANCE);
+        }
+    } else {
+        pulse->originY = (int)randf(State->height / PULSE_SIZE) * PULSE_SIZE;
+        pulse->dy = 0;
+        if (randf(1) > 0.5f) {
+            // Left
+            pulse->originX = 0;
+            pulse->dx = randf2(1.0f - SPEED_VARIANCE, 1.0 + SPEED_VARIANCE);
+        } else {
+            // Right
+            pulse->originX = State->width * 2;
+            pulse->dx = -randf2(1.0f - SPEED_VARIANCE, 1.0 + SPEED_VARIANCE);
+        }
+    }
+    pulse->startTime = gNow + (int)randf(MAX_DELAY);
+    
+    pulse->color = (int)randf(4.0f);
+    
+    pulse->pulseType = pulseType;
+    if (pulseType == PULSE_EXTRA) {
+        pulse->active = 0;
+    } else {
+        pulse->active = 1;
+    }
+}
+
+void initPulses() {
+    gNow = uptimeMillis();
+    int i;
+    for (i=0; i<MAX_PULSES; i++) {
+        initPulse(&gPulses[i], PULSE_NORMAL);
+    }
+    for (i=0; i<MAX_EXTRAS; i++) {
+        struct pulse_s * p = &gExtras[i];
+        p->pulseType = PULSE_EXTRA;
+        p->active = 0;
+    }
+}
+
+void drawBackground(int width, int height) {
+    bindTexture(NAMED_PFTexture, 0, NAMED_TBackground);
+    color(1.0f, 1.0f, 1.0f, 1.0f);
+    if (State->rotate) {
+        drawRect(0.0f, 0.0f, height*2, width, 0.0f);
+    } else {
+       drawRect(0.0f, 0.0f, width*2, height, 0.0f);
+       }
+}
+
+
+void drawPulses(struct pulse_s * pulseSet, int setSize) {
+       bindProgramFragment(NAMED_PFTexture);
+    bindProgramFragmentStore(NAMED_PSBlend);
+
+    float matrix[16];
+    
+    int i;
+    for (i=0; i<setSize; i++) {
+       struct pulse_s * p = &pulseSet[i];
+       
+           int delta = gNow - p->startTime;
+                       
+       if (p->active != 0 && delta >= 0) {
+                
+               float x = p->originX + (p->dx * SPEED * delta);
+               float y = p->originY + (p->dy * SPEED * delta);
+                
+               matrixLoadIdentity(matrix);
+               if (p->dx < 0) {
+                   vpLoadTextureMatrix(matrix);
+                   float xx = x + (TRAIL_SIZE * PULSE_SIZE);
+                   if (xx <= 0) {
+                       initPulse(p, p->pulseType);
+                   } else {
+                       setColor(p->color);
+                       bindTexture(NAMED_PFTexture, 0, NAMED_TPulse);            
+                       drawRect(x, y, xx, y + PULSE_SIZE, 0.0f);
+                       bindTexture(NAMED_PFTexture, 0, NAMED_TGlow);
+                       drawRect(x + HALF_PULSE_SIZE - HALF_GLOW_SIZE,
+                           y + HALF_PULSE_SIZE - HALF_GLOW_SIZE,
+                           x + HALF_PULSE_SIZE + HALF_GLOW_SIZE, 
+                           y + HALF_PULSE_SIZE + HALF_GLOW_SIZE,
+                           0.0f);
+                   }
+               } else if (p->dx > 0) {
+                   matrixRotate(matrix, 180.0f, 0.0f, 0.0f, 1.0f);
+                   vpLoadTextureMatrix(matrix);
+                   float xx = x - (TRAIL_SIZE * PULSE_SIZE);
+                       if (xx >= State->width * 2) {
+                      initPulse(p, p->pulseType);
+                   } else {
+                       setColor(p->color);
+                       bindTexture(NAMED_PFTexture, 0, NAMED_TPulse);          
+                       drawRect(xx, y, x, y + PULSE_SIZE, 0.0f);
+                       bindTexture(NAMED_PFTexture, 0, NAMED_TGlow);
+                       drawRect(x - HALF_PULSE_SIZE - HALF_GLOW_SIZE,
+                           y + HALF_PULSE_SIZE - HALF_GLOW_SIZE,
+                           x - HALF_PULSE_SIZE + HALF_GLOW_SIZE, 
+                           y + HALF_PULSE_SIZE + HALF_GLOW_SIZE,
+                           0.0f);
+                   }
+               } else if (p->dy < 0) {
+                   matrixRotate(matrix, -90.0f, 0.0f, 0.0f, 1.0f);
+                   vpLoadTextureMatrix(matrix);
+                   float yy = y + (TRAIL_SIZE * PULSE_SIZE);
+                   if (yy <= 0) {
+                      initPulse(p, p->pulseType);
+                   } else {
+                       setColor(p->color);
+                       bindTexture(NAMED_PFTexture, 0, NAMED_TPulse);
+                       drawRect(x, y, x + PULSE_SIZE, yy, 0.0f);
+                       bindTexture(NAMED_PFTexture, 0, NAMED_TGlow);
+                       drawRect(x + HALF_PULSE_SIZE - HALF_GLOW_SIZE,
+                           y + HALF_PULSE_SIZE - HALF_GLOW_SIZE,
+                           x + HALF_PULSE_SIZE + HALF_GLOW_SIZE, 
+                           y + HALF_PULSE_SIZE + HALF_GLOW_SIZE,
+                           0.0f);
+                   }
+               } else if (p->dy > 0) {
+                   matrixRotate(matrix, 90.0f, 0.0f, 0.0f, 1.0f);
+                   vpLoadTextureMatrix(matrix);
+                   float yy = y - (TRAIL_SIZE * PULSE_SIZE);
+                   if (yy >= State->height) {
+                      initPulse(p, p->pulseType);
+                   } else {
+                       setColor(p->color);
+                       bindTexture(NAMED_PFTexture, 0, NAMED_TPulse);
+                       drawRect(x, yy, x + PULSE_SIZE, y, 0.0f);
+                       bindTexture(NAMED_PFTexture, 0, NAMED_TGlow);
+                       drawRect(x + HALF_PULSE_SIZE - HALF_GLOW_SIZE,
+                           y - HALF_PULSE_SIZE - HALF_GLOW_SIZE,
+                           x + HALF_PULSE_SIZE + HALF_GLOW_SIZE, 
+                           y - HALF_PULSE_SIZE + HALF_GLOW_SIZE,
+                           0.0f);
+                   }
+               }
+           }
+    }
+    
+    
+    matrixLoadIdentity(matrix);
+    vpLoadTextureMatrix(matrix);
+}
+
+void addTap(int x, int y) {
+    int i;
+    int count = 0;
+    int color = (int)randf(4.0f);
+    x = (int)(x / PULSE_SIZE) * PULSE_SIZE;
+    y = (int)(y / PULSE_SIZE) * PULSE_SIZE;   
+    for (i=0; i<MAX_EXTRAS; i++) {
+       struct pulse_s * p = &gExtras[i];
+       if (p->active == 0) {
+            p->originX = x;
+            p->originY = y;
+            
+            if (count == 0) { 
+                p->dx = 1.5f;
+                p->dy = 0.0f;
+            } else if (count == 1) {
+                p->dx = -1.5f;
+                p->dy = 0.0f;
+            } else if (count == 2) {
+                p->dx = 0.0f;
+                p->dy = 1.5f;
+            } else if (count == 3) {
+                p->dx = 0.0f;
+                p->dy = -1.5f;
+            }
+            
+            p->active = 1;
+            p->color = color;
+            color++;
+            if (color >= 4) {
+                color = 0;
+            }
+            p->startTime = gNow;
+            count++;
+            if (count == 4) {
+                break;
+            }
+        }
+    }
+}
+
+int main(int index) {
+
+    gNow = uptimeMillis();
+    
+    if (Command->command != 0) {
+        debugF("x", Command->x);
+        debugF("y", Command->y);
+        Command->command = 0;
+        addTap(Command->x, Command->y);
+    }
+    
+    int width = State->width;
+    int height = State->height;
+    
+    float matrix[16];
+    matrixLoadIdentity(matrix);
+    if (State->rotate) {
+        //matrixLoadRotate(matrix, 90.0f, 0.0f, 0.0f, 1.0f);
+        //matrixTranslate(matrix, 0.0f, -height, 1.0f);
+    } else {
+         matrixTranslate(matrix, -(State->xOffset * width), 0, 0);
+    }
+    
+    vpLoadModelMatrix(matrix);
+        
+    drawBackground(width, height);
+
+    drawPulses(gPulses, MAX_PULSES);
+    drawPulses(gExtras, MAX_EXTRAS);
+           
+    return 1;
+}
diff --git a/src/com/android/wallpaper/nexus/NexusRS.java b/src/com/android/wallpaper/nexus/NexusRS.java
new file mode 100644 (file)
index 0000000..c93e875
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wallpaper.nexus;
+
+import static android.renderscript.Element.RGBA_8888;
+import static android.renderscript.Element.RGB_565;
+import static android.renderscript.ProgramFragment.EnvMode.MODULATE;
+import static android.renderscript.ProgramFragment.EnvMode.REPLACE;
+import static android.renderscript.ProgramStore.DepthFunc.ALWAYS;
+import static android.renderscript.Sampler.Value.LINEAR;
+import static android.renderscript.Sampler.Value.WRAP;
+
+import com.android.wallpaper.R;
+import com.android.wallpaper.RenderScriptScene;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.renderscript.Allocation;
+import android.renderscript.ProgramFragment;
+import android.renderscript.ProgramStore;
+import android.renderscript.ProgramVertex;
+import android.renderscript.Sampler;
+import android.renderscript.Script;
+import android.renderscript.ScriptC;
+import android.renderscript.Type;
+import android.renderscript.ProgramStore.BlendDstFunc;
+import android.renderscript.ProgramStore.BlendSrcFunc;
+import android.view.SurfaceHolder;
+
+import java.util.TimeZone;
+
+class NexusRS extends RenderScriptScene {
+
+    private static final int RSID_STATE = 0;
+    
+    private static final int RSID_COMMAND = 1;
+
+    private static final int TEXTURES_COUNT = 3;
+
+    private final BitmapFactory.Options mOptionsARGB = new BitmapFactory.Options();
+
+    private ProgramFragment mPfTexture;
+    
+    private ProgramFragment mPfColor;
+
+    private ProgramStore mPsSolid;
+    
+    private ProgramStore mPsBlend;
+    
+    private ProgramVertex mPvOrtho;
+    
+    private ProgramVertex.MatrixAllocation mPvOrthoAlloc;
+
+    private Sampler mSampler;
+
+    private Allocation mState;
+    
+    private Type mStateType;
+
+    private WorldState mWorldState;
+    
+    private Allocation mCommandAllocation;
+    
+    private Type mCommandType;
+
+    private CommandState mCommand;
+
+    public NexusRS(int width, int height) {
+        super(width, height);
+
+        mOptionsARGB.inScaled = false;
+        mOptionsARGB.inPreferredConfig = Bitmap.Config.ARGB_8888;
+    }
+
+    @Override
+    public void setOffset(float xOffset, float yOffset, int xPixels, int yPixels) {
+        mWorldState.xOffset = xOffset;
+        mState.data(mWorldState);
+    }
+    
+    @Override
+    public void start() {
+        super.start();
+    }
+
+    @Override
+    public void resize(int width, int height) {
+        super.resize(width, height);
+
+        mWorldState.width = width;
+        mWorldState.height = height;
+        mWorldState.rotate = width > height ? 1 : 0;
+        mState.data(mWorldState);
+
+        mPvOrthoAlloc.setupOrthoWindow(mWidth, mHeight);
+    }
+
+    @Override
+    protected ScriptC createScript() {
+        createProgramVertex();
+        createProgramFragmentStore();
+        createProgramFragment();
+        createState();
+        loadTextures();
+
+        ScriptC.Builder sb = new ScriptC.Builder(mRS);
+        sb.setType(mStateType, "State", RSID_STATE);
+        sb.setType(mCommandType, "Command", RSID_COMMAND);
+        sb.setScript(mResources, R.raw.nexus);
+        Script.Invokable invokable = sb.addInvokable("initPulses");
+        sb.setRoot(true);
+
+        ScriptC script = sb.create();
+        script.setClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+        script.setTimeZone(TimeZone.getDefault().getID());
+
+        script.bindAllocation(mState, RSID_STATE);
+        script.bindAllocation(mCommandAllocation, RSID_COMMAND);
+        
+        invokable.execute();
+        
+        return script;
+    }
+
+    static class WorldState {
+        public int width;
+        public int height;
+        public float glWidth;
+        public float glHeight;
+        public int rotate;
+        public int isPreview;
+        public float xOffset;
+    }
+
+    static class CommandState {
+        public int x;
+        public int y;
+        public int command;
+    }
+
+    private void createState() {
+        mWorldState = new WorldState();
+        mWorldState.width = mWidth;
+        mWorldState.height = mHeight;
+        mWorldState.rotate = mWidth > mHeight ? 1 : 0;
+        mWorldState.isPreview = isPreview() ? 1 : 0;
+
+        mStateType = Type.createFromClass(mRS, WorldState.class, 1, "WorldState");
+        mState = Allocation.createTyped(mRS, mStateType);
+        mState.data(mWorldState);
+        
+        mCommand = new CommandState();
+        mCommand.x = -1;
+        mCommand.y = -1;
+        mCommand.command = 0;
+
+        mCommandType = Type.createFromClass(mRS, CommandState.class, 1, "DropState");
+        mCommandAllocation = Allocation.createTyped(mRS, mCommandType);
+        mCommandAllocation.data(mCommand);
+    }
+
+    private void loadTextures() {
+        final Allocation[] textures = new Allocation[TEXTURES_COUNT];
+        textures[0] = loadTexture(R.drawable.pyramid_background, "TBackground");
+        textures[1] = loadTextureARGB(R.drawable.pulse, "TPulse");        
+        textures[2] = loadTextureARGB(R.drawable.glow, "TGlow");   
+        
+        final int count = textures.length;
+        for (int i = 0; i < count; i++) {
+            textures[i].uploadToTexture(0);
+        }
+    }
+
+    private Allocation loadTexture(int id, String name) {
+        final Allocation allocation = Allocation.createFromBitmapResource(mRS, mResources,
+                id, RGB_565(mRS), false);
+        allocation.setName(name);
+        return allocation;
+    }
+
+    private Allocation loadTextureARGB(int id, String name) {
+        Bitmap b = BitmapFactory.decodeResource(mResources, id, mOptionsARGB);
+        final Allocation allocation = Allocation.createFromBitmap(mRS, b, RGBA_8888(mRS), false);
+        allocation.setName(name);
+        return allocation;
+    }
+
+    private void createProgramFragment() {
+        Sampler.Builder sampleBuilder = new Sampler.Builder(mRS);
+        sampleBuilder.setMin(LINEAR);
+        sampleBuilder.setMag(LINEAR);
+        sampleBuilder.setWrapS(WRAP);
+        sampleBuilder.setWrapT(WRAP);
+        mSampler = sampleBuilder.create();
+
+        ProgramFragment.Builder builder = new ProgramFragment.Builder(mRS, null, null);
+        builder.setTexEnable(true, 0);
+        builder.setTexEnvMode(MODULATE, 0);
+        mPfTexture = builder.create();
+        mPfTexture.setName("PFTexture");
+        mPfTexture.bindSampler(mSampler, 0);
+        
+        builder = new ProgramFragment.Builder(mRS, null, null);
+        builder.setTexEnable(true, 0);
+        builder.setTexEnvMode(REPLACE, 0);
+        mPfColor = builder.create();
+        mPfColor.setName("PFColor");
+        mPfColor.bindSampler(mSampler, 0);
+    }
+
+    private void createProgramFragmentStore() {
+        ProgramStore.Builder builder = new ProgramStore.Builder(mRS, null, null);
+        builder.setDepthFunc(ALWAYS);
+        builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ONE);
+        builder.setDitherEnable(false);
+        builder.setDepthMask(true);
+        mPsSolid = builder.create();
+        mPsSolid.setName("PSSolid");
+        
+        builder = new ProgramStore.Builder(mRS, null, null);
+        builder.setDepthFunc(ALWAYS);
+       //  builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE_MINUS_SRC_ALPHA);
+        builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE);
+
+        builder.setDitherEnable(false);
+        builder.setDepthMask(true);
+        mPsBlend = builder.create();
+        mPsBlend.setName("PSBlend");
+    }
+
+    private void createProgramVertex() {
+        mPvOrthoAlloc = new ProgramVertex.MatrixAllocation(mRS);
+        mPvOrthoAlloc.setupOrthoWindow(mWidth, mHeight);
+
+        ProgramVertex.Builder pvb = new ProgramVertex.Builder(mRS, null, null);
+        pvb.setTextureMatrixEnable(true);
+        mPvOrtho = pvb.create();
+        mPvOrtho.bindAllocation(mPvOrthoAlloc);
+        mPvOrtho.setName("PVOrtho");
+    }
+
+    @Override
+    public Bundle onCommand(String action, int x, int y, int z, Bundle extras,
+            boolean resultRequested) {
+
+        final int dw = mWorldState.width;
+        final int bw = 960;
+        x = (int) (x + mWorldState.xOffset * (bw-dw));
+        
+        if ("android.wallpaper.tap".equals(action)) {
+            sendCommand(1, x, y);
+        } else if ("android.home.drop".equals(action)) {
+            sendCommand(2, x, y);
+        }
+        return null;
+    }
+
+    private void sendCommand(int command, int x, int y) {
+        mCommand.x = x;
+        mCommand.y = y;
+        mCommand.command = command;
+        mCommandAllocation.data(mCommand);
+    }
+}
\ No newline at end of file
index b393616..ce84bf5 100644 (file)
 
 package com.android.wallpaper.nexus;
 
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.BlurMaskFilter;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.LightingColorFilter;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.service.wallpaper.WallpaperService;
-import android.util.MathUtils;
-import android.view.SurfaceHolder;
-import android.view.animation.AnimationUtils;
+import com.android.wallpaper.RenderScriptWallpaper;
+import com.android.wallpaper.RenderScriptScene;
 
-import java.util.Set;
-import java.util.HashSet;
-
-import com.android.wallpaper.R;
-
-public class NexusWallpaper extends WallpaperService {
-
-    private static final int NUM_PULSES = 12;
-    private static final int MAX_PULSES = 32;
-    private static final int PULSE_SIZE = 16;
-    private static final int MAX_ALPHA = 128; // 0..255
-    private static final int PULSE_DELAY = 5000; // random restart time, in ms
-    private static final float ALPHA_DECAY = 0.85f;
-
-    private static final boolean ACCEPTS_TAP = true;
-
-    private static final int ANIMATION_PERIOD = 1000/50; // in ms^-1
-
-    private static final int[] PULSE_COLORS = {
-        0xFF0066CC, 0xDDFF0000, 0xBBFFCC00, 0xEE009900,
-    };
-
-    private static final String LOG_TAG = "Nexus";
-
-    private final Handler mHandler = new Handler();
-
-    public Engine onCreateEngine() {
-        return new NexusEngine();
-    }
-
-    class NexusEngine extends Engine {
-
-        class Automaton {
-            public void step(long now) { }
-            public void draw(Canvas c) { }
-        }
-
-        class Pulse extends Automaton {
-            Point v;
-            Point[] pts;
-            int start, len; // pointers into pts
-            Paint paint;
-            Paint glowPaint;
-            long startTime;
-            boolean started;
-
-            public float zagProb = 0.007f;
-            public int speed = 1;
-
-            public Pulse() {
-                v = new Point(0,0);
-                pts = new Point[PULSE_SIZE];
-                for (int i=0; i<pts.length; i++) {
-                    pts[i] = new Point(0,0);
-                }
-                paint = new Paint(Paint.FILTER_BITMAP_FLAG|Paint.DITHER_FLAG);
-                paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
-
-                glowPaint = new Paint(paint);
-                glowPaint.setAlpha((int)(MAX_ALPHA*0.7f));
-
-                start = len = 0;
-            }
-            public Pulse(long now, int x, int y, int dx, int dy) {
-                this();
-                start(now, x, y, dx, dy);
-            }
-
-            boolean isDiagonal() {
-                return v.x != 0 && v.y != 0;
-            }
-
-            public void zag() {
-                // take a random 90-degree turn
-                if (isDiagonal()) {
-                    if (Math.random() < 0.5) {
-                        v.x *= -1;
-                    } else {
-                        v.y *= -1;
-                    }
-                } else {
-                    int t = v.x; v.x = v.y; v.y = t;
-                    if (Math.random() < 0.5) {
-                        v.negate();
-                    }
-                }
-            }
-
-            public void start(long now, int x, int y, int dx, int dy) {
-                start = 0;
-                len = 1;
-                pts[start].set(x, y);
-                v.x = dx;
-                v.y = dy;
-                startTime = now;
-                setColor(PULSE_COLORS[(int)Math.floor(Math.random()*PULSE_COLORS.length)]);
-                started = false;
-            }
-
-            public void setColor(int c) {
-                paint.setColor(c);
-                glowPaint.setColorFilter(new LightingColorFilter(paint.getColor(), 0));
-            }
-
-            public void startRandomEdge(long now, boolean diag) {
-                int x, y;
-                if (Math.random() < 0.5) {
-                    // top or bottom edge
-                    x = (int)(Math.random() * mColumnCount);
-                    if (Math.random() < 0.5) {
-                        v.y = 1;
-                        y = 0;
-                    } else {
-                        v.y = -1;
-                        y = mRowCount;
-                    }
-                    v.x = diag ? ((Math.random() < 0.5) ? 1 : -1) : 0;
-                } else {
-                    // left or right edge
-                    y = (int)(Math.random() * mRowCount);
-                    if (Math.random() < 0.5) {
-                        v.set(1, 1);
-                        x = 0;
-                    } else {
-                        v.set(-1, 1);
-                        x = mColumnCount;
-                    }
-                    v.y = diag ? ((Math.random() < 0.5) ? 1 : -1) : 0;
-                }
-                start = 0;
-                len = 1;
-                pts[start].set(x, y);
-
-                startTime = now + (long)(Math.random() * PULSE_DELAY);
-
-                /* random colors
-                final float hsv[] = {(float)Math.random()*360, 0.75f, 1.0f};
-                int color = Color.HSVToColor(hsv);
-                */
-                // select colors
-                setColor(PULSE_COLORS[(int)Math.floor(Math.random()*PULSE_COLORS.length)]);
-                started = false;
-            }
-
-            public Point getHead() {
-                return pts[(start+len-1)%PULSE_SIZE];
-            }
-            public Point getTail() {
-                return pts[start];
-            }
-
-            public void step(long now) {
-                if (now < startTime) return;
-                started = true;
-
-                for (int i=0; i<speed; i++) {
-                    final Point neck = getHead();
-                    if (len < PULSE_SIZE) len++;
-                    else start = (start+1)%PULSE_SIZE;
-
-                    getHead().set(neck.x + v.x,
-                                  neck.y + v.y);
-                }
-
-                if (Math.random() < zagProb) {
-                    zag();
-                }
-            }
-
-            public void draw(Canvas c) {
-                if (!started) return;
-                boolean onScreen = false;
-                int a = MAX_ALPHA;
-
-                Point head = getHead();
-                c.drawBitmap(mGlow, (head.x-1)*mCellSize, (head.y-1)*mCellSize, glowPaint);
-
-                final Rect r = new Rect(0, 0, mCellSize, mCellSize);
-                for (int i=len-1; i>=0; i--) {
-                    paint.setAlpha(a);
-                    a *= ALPHA_DECAY;
-                    if (a < 0.05f) break; // note: you should decrease PULSE_SIZE
-                    Point p = pts[(start+i)%PULSE_SIZE];
-                    r.offsetTo(p.x * mCellSize, p.y * mCellSize);
-                    c.drawRect(r, paint);
-                    if (!onScreen)
-                        onScreen = !(p.x < 0 || p.x > mColumnCount || p.y < 0 || p.y > mRowCount);
-                }
-
-
-                if (!onScreen) {
-                    // Time to die.
-                    recycleOrRemovePulse(this);
-                }
-            }
-        }
-
-        private final Runnable mDrawNexus = new Runnable() {
-            public void run() {
-                drawFrame();
-                step();
-            }
-        };
-
-        private boolean mVisible;
-
-        private float mOffsetX;
-
-        private Bitmap mBackground;
-
-        private Bitmap mGreenLed;
-        
-        private Bitmap mGlow;
-
-        private Set<Automaton> mPulses = new HashSet<Automaton>();
-        private Set<Automaton> mDeadPulses = new HashSet<Automaton>();
-
-        private int mColumnCount;
-        private int mRowCount;
-
-        private int mCellSize;
-
-        private int mBackgroundWidth;
-        private int mBackgroundHeight;
-
-        NexusEngine() {
-        }
-
-        @Override
-        public void onCreate(SurfaceHolder surfaceHolder) {
-            super.onCreate(surfaceHolder);
-
-            Resources r = getResources();
-
-            mBackground = BitmapFactory.decodeResource(r, R.drawable.pyramid_background, null);
-
-            mBackgroundWidth = mBackground.getWidth();
-            mBackgroundHeight = mBackground.getHeight();
-
-            mGreenLed = BitmapFactory.decodeResource(r, R.drawable.led_green, null);
-
-            mCellSize = mGreenLed.getWidth();
-
-            mGlow = Bitmap.createBitmap(3*mCellSize, 3*mCellSize, Bitmap.Config.ARGB_8888);
-            Canvas c = new Canvas(mGlow);
-            Paint p = new Paint();
-            final int halfCell = mCellSize/2;
-            p.setMaskFilter(new BlurMaskFilter(halfCell, BlurMaskFilter.Blur.NORMAL));
-            p.setColor(Color.WHITE);
-            c.drawRect(halfCell, halfCell, 5*halfCell, 5*halfCell, p);
-
-            initializeState();
-
-            if (isPreview()) {
-                mOffsetX = 0.5f;
-            }
-        }
-
-        private void initializeState() {
-            mColumnCount = mBackgroundWidth / mCellSize;
-            mRowCount = mBackgroundHeight / mCellSize;
-            mPulses.clear();
-            mDeadPulses.clear();
-
-            final long now = AnimationUtils.currentAnimationTimeMillis();
-
-            if (isPreview()) {
-                mOffsetX = 0.5f;
-            }
-        }
-
-        @Override
-        public void onDestroy() {
-            super.onDestroy();
-            mHandler.removeCallbacks(mDrawNexus);
-        }
-
-        @Override
-        public void onVisibilityChanged(boolean visible) {
-            mVisible = visible;
-            if (!visible) {
-                mHandler.removeCallbacks(mDrawNexus);
-            }
-            drawFrame();
-        }
-
-        @Override
-        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-            super.onSurfaceChanged(holder, format, width, height);
-            initializeState();
-            drawFrame();
-        }
-
-        @Override
-        public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) {
-            super.onDesiredSizeChanged(desiredWidth, desiredHeight);
-            initializeState();
-            drawFrame();
-        }
-
-        @Override
-        public void onSurfaceCreated(SurfaceHolder holder) {
-            super.onSurfaceCreated(holder);
-        }
-
-        @Override
-        public void onSurfaceDestroyed(SurfaceHolder holder) {
-            super.onSurfaceDestroyed(holder);
-            mVisible = false;
-            mHandler.removeCallbacks(mDrawNexus);
-        }
-
-        @Override
-        public void onOffsetsChanged(float xOffset, float yOffset,
-                float xStep, float yStep, int xPixels, int yPixels) {
-            super.onOffsetsChanged(xOffset, yOffset, xStep, yStep, xPixels, yPixels);
-            mOffsetX = xOffset;
-            drawFrame();
-        }
-
-
-        @Override
-        public Bundle onCommand(String action, int x, int y, int z, Bundle extras,
-                boolean resultRequested) {
-            if (ACCEPTS_TAP && "android.wallpaper.tap".equals(action)) {
-
-                final SurfaceHolder holder = getSurfaceHolder();
-                final Rect frame = holder.getSurfaceFrame();
-
-                final int dw = frame.width();
-                final int bw = mBackgroundWidth;
-                final int cellX = (int)((x + mOffsetX * (bw-dw)) / mCellSize);
-                final int cellY = (int)(y / mCellSize);
-                
-                int colorIdx = (int)(Math.random() * PULSE_COLORS.length);
-
-                Pulse p = new Pulse();
-                p.zagProb = 0;
-                p.start(0, cellX, cellY, 0, 1);
-                p.setColor(PULSE_COLORS[colorIdx]);
-                addPulse(p);
-                colorIdx = (colorIdx + 1) % PULSE_COLORS.length;
-
-                p = new Pulse();
-                p.zagProb = 0;
-                p.start(0, cellX, cellY, 1, 0);
-                p.setColor(PULSE_COLORS[colorIdx]);
-                addPulse(p);
-                colorIdx = (colorIdx + 1) % PULSE_COLORS.length;
-
-                p = new Pulse();
-                p.zagProb = 0;
-                p.start(0, cellX, cellY, -1, 0);
-                p.setColor(PULSE_COLORS[colorIdx]);
-                addPulse(p);
-                colorIdx = (colorIdx + 1) % PULSE_COLORS.length;
-
-                p = new Pulse();
-                p.zagProb = 0;
-                p.start(0, cellX, cellY, 0, -1);
-                p.setColor(PULSE_COLORS[colorIdx]);
-                addPulse(p);
-                colorIdx = (colorIdx + 1) % PULSE_COLORS.length;
-
-            } else if ("android.home.drop".equals(action)) {
-                final SurfaceHolder holder = getSurfaceHolder();
-                final Rect frame = holder.getSurfaceFrame();
-
-                final int dw = frame.width();
-                final int bw = mBackgroundWidth;
-                final int cellX = (int)((x + mOffsetX * (bw-dw)) / mCellSize);
-                final int cellY = (int)(y / mCellSize);
-                Pulse p = new Pulse();
-                p.zagProb = 0;
-                p.start(0, cellX, cellY, 0, 1);
-                addPulse(p);
-                p = new Pulse();
-                p.zagProb = 0;
-                p.start(0, cellX, cellY, 1, 0);
-                addPulse(p);
-                p = new Pulse();
-                p.zagProb = 0;
-                p.start(0, cellX, cellY, -1, 0);
-                addPulse(p);
-                p = new Pulse();
-                p.zagProb = 0;
-                p.start(0, cellX, cellY, 0, -1);
-                addPulse(p);
-
-                p = new Pulse();
-                p.zagProb = 0;
-                p.start(0, cellX, cellY, -1, -1);
-                addPulse(p);
-                p = new Pulse();
-                p.zagProb = 0;
-                p.start(0, cellX, cellY, 1, -1);
-                addPulse(p);
-                p = new Pulse();
-                p.zagProb = 0;
-                p.start(0, cellX, cellY, 1, 1);
-                addPulse(p);
-                p = new Pulse();
-                p.zagProb = 0;
-                p.start(0, cellX, cellY, -1, 1);
-                addPulse(p);
-            }
-            return null;
-        }
-
-        void addPulse(Pulse p) {
-            if (mPulses.size() > MAX_PULSES) return;
-            mPulses.add(p);
-        }
-
-        void removePulse(Pulse p) {
-            mDeadPulses.add(p);
-        }
-
-        void recycleOrRemovePulse(Pulse p) {
-            if (mPulses.size() < NUM_PULSES) {
-                p.startRandomEdge(AnimationUtils.currentAnimationTimeMillis(), false);
-            } else {
-                removePulse(p);
-            }
-        }
-
-        void step() {
-            final long now = AnimationUtils.currentAnimationTimeMillis();
-
-            // not enough pulses? add some
-            for (int i=mPulses.size(); i<NUM_PULSES; i++) {
-                Pulse p = new Pulse();
-                p.startRandomEdge(now, false);
-                mPulses.add(p);
-            }
-
-            for (Automaton p : mDeadPulses) {
-                mPulses.remove(p);
-            }
-            mDeadPulses.clear();
-
-            // update state
-            for (Automaton p : mPulses) {
-                p.step(now);
-            }
-        }
-
-        void drawFrame() {
-            final SurfaceHolder holder = getSurfaceHolder();
-            final Rect frame = holder.getSurfaceFrame();
-
-            Canvas c = null;
-            try {
-                c = holder.lockCanvas();
-                if (c != null) {
-                    final int dw = frame.width();
-                    final int bw = mBackgroundWidth;
-                    final int availw = dw-bw;
-                    final int xPixels = availw < 0 ? (int)(availw*mOffsetX+.5f) : (availw/2);
-
-                    c.translate(xPixels, 0);
-
-                    c.drawBitmap(mBackground, 0, 0, null);
-                    for (Automaton p : mPulses) {
-                        p.draw(c);
-                    }
-                }
-            } finally {
-                if (c != null) holder.unlockCanvasAndPost(c);
-            }
-
-            mHandler.removeCallbacks(mDrawNexus);
-            if (mVisible) {
-                mHandler.postDelayed(mDrawNexus, ANIMATION_PERIOD);
-            }
-        }
+public class NexusWallpaper extends RenderScriptWallpaper {
+    protected RenderScriptScene createScene(int width, int height) {
+        return new NexusRS(width, height);
     }
 }