package xyz.klinker.blur.addons.settings.bubble_tutorial; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.os.Bundle; import android.preference.PreferenceManager; import android.util.TypedValue; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import xyz.klinker.blur.R; import java.text.DecimalFormat; public class TutorialActivity extends Activity { private static final int STATE_LOADING = 0; private static final int STATE_B1_E = 1; private static final int STATE_T1_I = 2; private static final int STATE_B1_W = 3; private static final int STATE_T1_O = 4; private static final int STATE_B1_S = 5; private static final int STATE_B2_E = 6; private static final int STATE_T2_I = 7; private static final int STATE_B2_W = 8; private static final int STATE_T2_O = 9; private static final int STATE_B2_S = 10; private static final int STATE_B3_E = 11; private static final int STATE_T3_I = 12; private static final int STATE_B3_W = 13; private static final int STATE_FINISHING = 14; private static final int STATE_MOVING_LOGO = 15; private static final int STATE_FADING_OUT = 16; public static final String ACTION_HIGHLIGHT_PAGE_PICKER = "xyz.klinker.blur.tutorial.PAGE_PICKER"; public static final String ACTION_START_HIGHLIGHT_DISPLAY = "xyz.klinker.blur.tutorial.DISPLAY"; public static final String ACTION_HIGHLIGHT_HELP = "xyz.klinker.blur.tutorial.HELP"; private DrawingThread thread; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new DrawingPanel(this)); } @Override public void onStop() { super.onStop(); thread.setRunning(false); finish(); overridePendingTransition(0, 0); PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("initial_tutorial", false).commit(); } private class DrawingPanel extends SurfaceView implements SurfaceHolder.Callback { private String avgFps; private double fps; private Circle[] circles; private int state = 0; private long startTime = System.currentTimeMillis(); private final String TEXT1; private final String TEXT2; private final String TEXT3; public DrawingPanel (Context context) { super(context); this.setBackgroundColor(Color.TRANSPARENT); this.setZOrderOnTop(true); getHolder().setFormat(PixelFormat.TRANSPARENT); getHolder().addCallback(this); setFocusable(true); thread = new DrawingThread(getHolder(), this); TEXT1 = "\nUse the\nPage Picker to\nadd content to\nBlur!"; TEXT2 = "\nDisplay Settings\nare for all\nof your\ncustomizations."; TEXT3 = "The Help\nsection is a great\nplace to learn\nmore about\nthe app."; } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { float circle1 = getResources().getDimensionPixelSize(R.dimen.tutorial_bubble_1_size); float circle2 = getResources().getDimensionPixelSize(R.dimen.tutorial_bubble_2_size); float circle3 = getResources().getDimensionPixelSize(R.dimen.tutorial_bubble_3_size); float padding = getResources().getDimensionPixelSize(R.dimen.tutorial_bubble_padding); circles = new Circle[] { new Circle(circle1, .3, .95, getResources().getColor(android.R.color.holo_red_dark), getWidth() - circle1 - padding, padding + circle1, 500), new Circle(circle2, .25, .9, getResources().getColor(android.R.color.holo_orange_light), padding * 3, getHeight() / 2 - 4 * padding, 3500), new Circle(circle3, .35, .85, getResources().getColor(android.R.color.holo_green_dark), getWidth() - circle3 - padding, getHeight() - circle3 - (padding * 2), 6500) }; thread.setRunning(true); thread.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { boolean retry = true; while (retry) { try { thread.join(); retry = false; } catch (InterruptedException e) { } } } @Override public boolean onTouchEvent(MotionEvent event) { if (state == STATE_B1_W && event.getAction() == MotionEvent.ACTION_DOWN) { state++; circles[0].startTime = System.currentTimeMillis() - (long) (1.04017 * circles[0].bounceTime * 1000); } else if (state == STATE_B2_W && event.getAction() == MotionEvent.ACTION_DOWN) { state++; circles[1].startTime = System.currentTimeMillis() - (long) (1.04017 * circles[1].bounceTime * 1000); } else if (state == STATE_B3_W && event.getAction() == MotionEvent.ACTION_DOWN) { state++; circles[2].startTime = System.currentTimeMillis(); } return super.onTouchEvent(event); } @Override protected void onDraw(Canvas canvas) { } private boolean needHighlightPagePicker = true; private boolean needHighlightDisplays = true; private boolean needHighlightHelp = true; public void update() { switch (state) { case STATE_LOADING: if (System.currentTimeMillis() - startTime >= 1000 && fps > 20) { state++; circles[0].startTime = System.currentTimeMillis(); } break; case STATE_B1_E: if (System.currentTimeMillis() - circles[0].startTime < circles[0].maxTime * 1000) { circles[0].radius = getRadiusNeeded(circles[0]); } else { state++; startTime = System.currentTimeMillis(); } break; case STATE_T1_I: textAlpha += 15; if (textAlpha >= 255) { textAlpha = 255; state++; startTime = System.currentTimeMillis(); } break; case STATE_B1_W: long time = System.currentTimeMillis(); if (time - startTime > 5000) { state++; circles[0].startTime = System.currentTimeMillis() - (long) (1.04017 * circles[0].bounceTime * 1000); } else if (time - startTime > 1000 && needHighlightPagePicker) { sendBroadcast(new Intent(ACTION_HIGHLIGHT_PAGE_PICKER)); needHighlightPagePicker = false; } break; case STATE_T1_O: textAlpha -= 15; if (textAlpha <= 0) { textAlpha = 0; state++; circles[0].startTime = System.currentTimeMillis() - (long) (1.04017 * circles[0].bounceTime * 1000); } break; case STATE_B1_S: circles[0].radius = getRadiusNeeded(circles[0]); if (circles[0].radius <= 0) { circles[0].radius = 0; state++; circles[1].startTime = System.currentTimeMillis(); } break; case STATE_B2_E: if (System.currentTimeMillis() - circles[1].startTime < circles[1].maxTime * 1000) { circles[1].radius = getRadiusNeeded(circles[1]); } else { state++; startTime = System.currentTimeMillis(); } break; case STATE_T2_I: textAlpha += 15; if (textAlpha >= 255) { textAlpha = 255; state++; startTime = System.currentTimeMillis(); } break; case STATE_B2_W: time = System.currentTimeMillis(); if (time - startTime > 5000) { state++; circles[1].startTime = System.currentTimeMillis() - (long) (1.04017 * circles[1].bounceTime * 1000); } else if (time - startTime > 1500 && needHighlightDisplays) { sendBroadcast(new Intent(ACTION_START_HIGHLIGHT_DISPLAY)); needHighlightDisplays = false; } break; case STATE_T2_O: textAlpha -= 15; if (textAlpha <= 0) { textAlpha = 0; state++; circles[1].startTime = System.currentTimeMillis() - (long) (1.04017 * circles[1].bounceTime * 1000); } break; case STATE_B2_S: circles[1].radius = getRadiusNeeded(circles[1]); if (circles[1].radius <= 0) { circles[1].radius = 0; state++; circles[2].startTime = System.currentTimeMillis(); } break; case STATE_B3_E: if (System.currentTimeMillis() - circles[2].startTime < circles[2].maxTime * 1000) { circles[2].radius = getRadiusNeeded(circles[2]); } else { state++; startTime = System.currentTimeMillis(); } break; case STATE_T3_I: textAlpha += 15; if (textAlpha >= 255) { textAlpha = 255; state++; startTime = System.currentTimeMillis(); } break; case STATE_B3_W: // just wait for a touch on the screen time = System.currentTimeMillis(); if (time - startTime > 1000 && needHighlightHelp) { sendBroadcast(new Intent(ACTION_HIGHLIGHT_HELP)); needHighlightHelp = false; } break; case STATE_FINISHING: circles[2].radius += 2.5 * Math.pow((System.currentTimeMillis() - circles[2].startTime) / 700.0, 4) + 8; textAlpha -= 5; if (textAlpha <= 0) { textAlpha = 0; } if (circles[2].y - circles[2].radius < TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -400, getResources().getDisplayMetrics())) { textAlpha = 0; state++; startTime = System.currentTimeMillis(); } break; case STATE_MOVING_LOGO: state++; /*logoY -= 2.5 * Math.pow((System.currentTimeMillis() - startTime) / 1000.0, 3) + 8; if (logoY < -1 * logo.getHeight()) { state++; circles[2].startTime = System.currentTimeMillis(); }*/ break; case STATE_FADING_OUT: alpha -= 4 * Math.pow((System.currentTimeMillis() - circles[2].startTime) / 1000.0, 2) + 8; if (alpha <= 0) { state++; } break; default: thread.setRunning(false); finish(); overridePendingTransition(0, 0); PreferenceManager.getDefaultSharedPreferences(TutorialActivity.this).edit().putBoolean("initial_tutorial", false).commit(); break; } } public int alpha = 255; public int textAlpha = 0; public int logoY = 0; public void render(Canvas canvas) { if (canvas != null) { canvas.drawColor(0, PorterDuff.Mode.CLEAR); //displayFps(canvas, avgFps); Paint paint = new Paint(); paint.setAntiAlias(true); switch (state) { case STATE_LOADING: // Dont need to draw anything break; case STATE_B1_E: case STATE_T1_I: case STATE_B1_W: case STATE_T1_O: case STATE_B1_S: // Draw the first circle at its state paint.setColor(circles[0].color); canvas.drawCircle(circles[0].x, circles[0].y, (float) circles[0].radius, paint); paint.setColor(getResources().getColor(android.R.color.white)); paint.setTextAlign(Paint.Align.CENTER); float textSize = (float) (1.5 * getResources().getDimensionPixelSize(R.dimen.tutorial_text_size)); paint.setTextSize(textSize); paint.setAlpha(textAlpha); String[] text = TEXT1.split("\n"); for (int i = 0; i < text.length; i++) { canvas.drawText(text[i], circles[0].x, circles[0].y + (textSize/2) - (text.length * textSize / 2) + (i * textSize), paint); } break; case STATE_B2_E: case STATE_T2_I: case STATE_B2_W: case STATE_T2_O: case STATE_B2_S: // Draw the second circle at its state paint.setColor(circles[1].color); canvas.drawCircle(circles[1].x, circles[1].y, (float) circles[1].radius, paint); paint.setColor(getResources().getColor(android.R.color.white)); paint.setTextAlign(Paint.Align.CENTER); textSize = (float) (1.5 * getResources().getDimensionPixelSize(R.dimen.tutorial_text_size)); paint.setTextSize(textSize); paint.setAlpha(textAlpha); text = TEXT2.split("\n"); for (int i = 0; i < text.length; i++) { canvas.drawText(text[i], circles[1].x, circles[1].y + (textSize/2) - (text.length * textSize / 2) + (i * textSize), paint); } break; case STATE_B3_E: case STATE_T3_I: case STATE_B3_W: case STATE_FINISHING: case STATE_MOVING_LOGO: case STATE_FADING_OUT: // Draw the third circle at its state paint.setColor(circles[2].color); paint.setAlpha(alpha); canvas.drawCircle(circles[2].x, circles[2].y, (float) circles[2].radius, paint); paint.setColor(getResources().getColor(android.R.color.white)); paint.setTextAlign(Paint.Align.CENTER); textSize = (float) (1.5 * getResources().getDimensionPixelSize(R.dimen.tutorial_text_size)); paint.setTextSize(textSize); paint.setAlpha(textAlpha); text = TEXT3.split("\n"); for (int i = 0; i < text.length; i++) { canvas.drawText(text[i], circles[2].x, circles[2].y + (textSize) - (text.length * textSize / 2) + (i * textSize), paint); } paint.setAlpha(alpha); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); //canvas.drawBitmap(logo, getWidth() / 2 - logo.getWidth() / 2, logoY, paint); break; } } } private double getRadiusNeeded(Circle circle) { long currentTime = System.currentTimeMillis(); double elapsed = ((currentTime - circle.startTime) / 1000.0); if (elapsed < 0) { return 0; } double time = elapsed - (2.04017 * circle.bounceTime); return -1 * (1 - circle.bounceSize) * (circle.height / Math.pow(circle.bounceTime, 4)) * Math.pow(time, 4) + 2 * (1 - circle.bounceSize) * (circle.height / (circle.bounceTime * circle.bounceTime)) * time * time + circle.bounceSize * circle.height; } public void setAvgFps(String avgFps) { this.avgFps = avgFps; } public void setFps(double fps) { this.fps = fps; } public void displayFps(Canvas canvas, String fps) { if (canvas != null && fps != null) { Paint paint = new Paint(); paint.setColor(Color.BLACK); paint.setTextSize(getResources().getDimensionPixelSize(R.dimen.tutorial_text_size)); canvas.drawText(fps, 100, 200, paint); } } } private class DrawingThread extends Thread { private final static int MAX_FPS = 30; private final static int MAX_FRAME_SKIPS = 5; private final static int FRAME_PERIOD = 1000 / MAX_FPS; private DecimalFormat df = new DecimalFormat("0.##"); private final static int STAT_INTERVAL = 1000; private final static int FPS_HISTORY_NR = 10; private long lastStatusStore = 0; private long statusIntervalTimer = 0l; private long totalFramesSkipped = 0l; private long framesSkippedPerStatCycle = 0l; private int frameCountPerStatCycle = 0; private long totalFrameCount = 0l; private double[] fpsStore; private long statsCount = 0; private double averageFps = 0.0; private boolean running; private SurfaceHolder holder; private DrawingPanel panel; public DrawingThread(SurfaceHolder holder, DrawingPanel panel) { super(); this.holder = holder; this.panel = panel; } public void setRunning(boolean running) { this.running = running; } @Override public void run() { Canvas canvas; initTimingElements(); long beginTime; long timeDiff; int sleepTime; int framesSkipped; sleepTime = 0; while (running) { canvas = null; try { canvas = this.holder.lockCanvas(); synchronized (holder) { beginTime = System.currentTimeMillis(); framesSkipped = 0; this.panel.update(); this.panel.render(canvas); timeDiff = System.currentTimeMillis() - beginTime; sleepTime = (int) (FRAME_PERIOD - timeDiff); if (sleepTime > 0) { try { Thread.sleep(sleepTime); } catch (Exception e) { } } while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) { this.panel.update(); sleepTime += FRAME_PERIOD; framesSkipped++; } framesSkippedPerStatCycle += framesSkipped; storeStats(); } } finally { if (canvas != null) { holder.unlockCanvasAndPost(canvas); } } } } private void storeStats() { frameCountPerStatCycle++; totalFrameCount++; statusIntervalTimer += (System.currentTimeMillis() - statusIntervalTimer); if (statusIntervalTimer >= lastStatusStore + STAT_INTERVAL) { double actualFps = (double) (frameCountPerStatCycle / (STAT_INTERVAL / 1000)); panel.setFps(actualFps); fpsStore[(int) statsCount % FPS_HISTORY_NR] = actualFps; statsCount++; double totalFps = 0.0; for (int i = 0; i < FPS_HISTORY_NR; i++) { totalFps += fpsStore[i]; } if (statsCount < FPS_HISTORY_NR) { averageFps = totalFps / statsCount; } else { averageFps = totalFps / FPS_HISTORY_NR; } totalFramesSkipped += framesSkippedPerStatCycle; framesSkippedPerStatCycle = 0; statusIntervalTimer = 0; frameCountPerStatCycle = 0; statusIntervalTimer = System.currentTimeMillis(); lastStatusStore = statusIntervalTimer; panel.setAvgFps("FPS: " + df.format(averageFps)); } } private void initTimingElements() { fpsStore = new double[FPS_HISTORY_NR]; for (int i = 0; i < FPS_HISTORY_NR; i++) { fpsStore[i] = 0.0; } } } }