| /* |
| * 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 android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.Paint; |
| import android.graphics.Point; |
| import android.graphics.PorterDuff; |
| 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 java.util.Set; |
| import java.util.HashSet; |
| |
| import com.android.wallpaper.R; |
| |
| public class NexusWallpaper extends WallpaperService { |
| |
| private static final int NUM_PULSES = 18; |
| private static final int MAX_PULSES = 42; |
| private static final int PULSE_SIZE = 16; |
| private static final int MAX_ALPHA = 192; // 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/30; // in ms^-1 |
| |
| private static final int[] PULSE_COLORS = { |
| 0xFF0066CC, 0xFFFF0000, 0xFFFFCC00, 0xFF009900, |
| }; |
| |
| 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; |
| long startTime; |
| boolean started; |
| |
| public float zagProb = 0.01f; |
| 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)); |
| |
| 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; |
| paint.setColor(PULSE_COLORS[(int)Math.floor(Math.random()*PULSE_COLORS.length)]); |
| started = false; |
| } |
| |
| 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 |
| paint.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; |
| 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 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(); |
| |
| 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); |
| 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); |
| } else if ("android.home.drop".equals(action)) { |
| // TODO: something awesome |
| } |
| 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); |
| } |
| } |
| } |
| } |