1 /*); 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui; 18 19 import android.animation.AnimatorSet; 20 import android.animation.PropertyValuesHolder; 21 import android.animation.ObjectAnimator; 22 import android.animation.TimeAnimator; 23 import android.app.Activity; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.PackageManager; 28 import android.graphics.drawable.AnimationDrawable; 29 import android.graphics.drawable.BitmapDrawable; 30 import android.graphics.Bitmap; 31 import android.graphics.Canvas; 32 import android.graphics.Color; 33 import android.graphics.ColorMatrix; 34 import android.graphics.ColorMatrixColorFilter; 35 import android.graphics.Matrix; 36 import android.graphics.Paint; 37 import android.graphics.Point; 38 import android.graphics.PorterDuffColorFilter; 39 import android.graphics.PorterDuffXfermode; 40 import android.graphics.Rect; 41 import android.graphics.RectF; 42 import android.os.Handler; 43 import android.os.SystemClock; 44 import android.provider.Settings; 45 import android.util.AttributeSet; 46 import android.util.DisplayMetrics; 47 import android.util.Pair; 48 import android.view.Gravity; 49 import android.view.MotionEvent; 50 import android.view.View; 51 import android.view.ViewGroup; 52 import android.view.WindowManager; 53 import android.view.animation.AnimationUtils; 54 import android.widget.FrameLayout; 55 import android.widget.ImageView; 56 import java.util.HashMap; 57 import java.util.Random; 58 59 public class BeanBag extends Activity { 60 final static boolean DEBUG = false; 61 62 public static class Board extends FrameLayout 63 { 64 static Random sRNG = new Random(); 65 66 static float lerp(float a, float b, float f) { 67 return (b-a)*f + a; 68 } 69 70 static float randfrange(float a, float b) { 71 return lerp(a, b, sRNG.nextFloat()); 72 } 73 74 static int randsign() { 75 return sRNG.nextBoolean() ? 1 : -1; 76 } 77 78 static boolean flip() { 79 return sRNG.nextBoolean(); 80 } 81 82 static float mag(float x, float y) { 83 return (float) Math.sqrt(x*x+y*y); 84 } 85 86 static float clamp(float x, float a, float b) { 87 return ((x<a)?a:((x>b)?b:x)); 88 } 89 90 static float dot(float x1, float y1, float x2, float y2) { 91 return x1*x2+y1+y2; 92 } 93 94 static <E> E pick(E[] array) { 95 if (array.length == 0) return null; 96 return array[sRNG.nextInt(array.length)]; 97 } 98 99 static int pickInt(int[] array) { 100 if (array.length == 0) return 0; 101 return array[sRNG.nextInt(array.length)]; 102 } 103 104 static int NUM_BEANS = 40; 105 static float MIN_SCALE = 0.2f; 106 static float MAX_SCALE = 1f; 107 108 static float LUCKY = 0.001f; 109 110 static int MAX_RADIUS = (int)(576 * MAX_SCALE); 111 112 static int BEANS[] = { 113 R.drawable.redbean0, 114 R.drawable.redbean0, 115 R.drawable.redbean0, 116 R.drawable.redbean0, 117 R.drawable.redbean1, 118 R.drawable.redbean1, 119 R.drawable.redbean2, 120 R.drawable.redbean2, 121 R.drawable.redbeandroid, 122 }; 123 124 static int COLORS[] = { 125 0xFF00CC00, 126 0xFFCC0000, 127 0xFF0000CC, 128 0xFFFFFF00, 129 0xFFFF8000, 130 0xFF00CCFF, 131 0xFFFF0080, 132 0xFF8000FF, 133 0xFFFF8080, 134 0xFF8080FF, 135 0xFFB0C0D0, 136 0xFFDDDDDD, 137 0xFF333333, 138 }; 139 140 public class Bean extends ImageView { 141 public static final float VMAX = 1000.0f; 142 public static final float VMIN = 100.0f; 143 144 public float x, y, a; 145 146 public float va; 147 public float vx, vy; 148 149 public float r; 150 151 public float z; 152 153 public int h,w; 154 155 public boolean grabbed; 156 public float grabx, graby; 157 public long grabtime; 158 private float grabx_offset, graby_offset; 159 160 public Bean(Context context, AttributeSet as) { 161 super(context, as); 162 } 163 164 public String toString() { 165 return String.format("<bean (%.1f, %.1f) (%d x %d)>", 166 getX(), getY(), getWidth(), getHeight()); 167 } 168 169 private void pickBean() { 170 int beanId = pickInt(BEANS); 171 if (randfrange(0,1) <= LUCKY) { 172 beanId = R.drawable.jandycane; 173 } 174 BitmapDrawable bean = (BitmapDrawable) getContext().getResources().getDrawable(beanId); 175 Bitmap beanBits = bean.getBitmap(); 176 h=beanBits.getHeight(); 177 w=beanBits.getWidth(); 178 179 if (DEBUG) { 180 bean.setAlpha(0x80); 181 } 182 this.setImageDrawable(bean); 183 184 Paint pt = new Paint(); 185 final int color = pickInt(COLORS); 186 ColorMatrix CM = new ColorMatrix(); 187 float[] M = CM.getArray(); 188 // we assume the color information is in the red channel 189 /* R */ M[0] = (float)((color & 0x00FF0000) >> 16) / 0xFF; 190 /* G */ M[5] = (float)((color & 0x0000FF00) >> 8) / 0xFF; 191 /* B */ M[10] = (float)((color & 0x000000FF)) / 0xFF; 192 pt.setColorFilter(new ColorMatrixColorFilter(M)); 193 setLayerType(View.LAYER_TYPE_HARDWARE, (beanId == R.drawable.jandycane) ? null : pt); 194 } 195 196 public void reset() { 197 pickBean(); 198 199 final float scale = lerp(MIN_SCALE,MAX_SCALE,z); 200 setScaleX(scale); setScaleY(scale); 201 202 r = 0.3f*Math.max(h,w)*scale; 203 204 a=(randfrange(0,360)); 205 va = randfrange(-30,30); 206 207 vx = randfrange(-40,40) * z; 208 vy = randfrange(-40,40) * z; 209 final float boardh = boardHeight; 210 final float boardw = boardWidth; 211 //android.util.Log.d("BeanBag", "reset: w="+w+" h="+h); 212 if (flip()) { 213 x=(vx < 0 ? boardw+2*r : -r*4f); 214 y=(randfrange(0, boardh-3*r)*0.5f + ((vy < 0)?boardh*0.5f:0)); 215 } else { 216 y=(vy < 0 ? boardh+2*r : -r*4f); 217 x=(randfrange(0, boardw-3*r)*0.5f + ((vx < 0)?boardw*0.5f:0)); 218 } 219 } 220 221 public void update(float dt) { 222 if (grabbed) { 223 // final float interval = (SystemClock.uptimeMillis() - grabtime) / 1000f; 224 vx = (vx * 0.75f) + ((grabx - x) / dt) * 0.25f; 225 x = grabx; 226 vy = (vy * 0.75f) + ((graby - y) / dt) * 0.25f;; 227 y = graby; 228 } else { 229 x = (x + vx * dt); 230 y = (y + vy * dt); 231 a = (a + va * dt); 232 } 233 } 234 235 public float overlap(Bean other) { 236 final float dx = (x - other.x); 237 final float dy = (y - other.y); 238 return mag(dx, dy) - r - other.r; 239 } 240 241 @Override 242 public boolean onTouchEvent(MotionEvent e) { 243 switch (e.getAction()) { 244 case MotionEvent.ACTION_DOWN: 245 grabbed = true; 246 grabx_offset = e.getRawX() - x; 247 graby_offset = e.getRawY() - y; 248 va = 0; 249 // fall 250 case MotionEvent.ACTION_MOVE: 251 grabx = e.getRawX() - grabx_offset; 252 graby = e.getRawY() - graby_offset; 253 grabtime = e.getEventTime(); 254 break; 255 case MotionEvent.ACTION_CANCEL: 256 case MotionEvent.ACTION_UP: 257 grabbed = false; 258 float a = randsign() * clamp(mag(vx, vy) * 0.33f, 0, 1080f); 259 va = randfrange(a*0.5f, a); 260 break; 261 } 262 return true; 263 } 264 } 265 266 TimeAnimator mAnim; 267 private int boardWidth; 268 private int boardHeight; 269 270 public Board(Context context, AttributeSet as) { 271 super(context, as); 272 273 setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); 274 275 setWillNotDraw(!DEBUG); 276 } 277 278 private void reset() { 279 // android.util.Log.d("Nyandroid", "board reset"); 280 removeAllViews(); 281 282 final ViewGroup.LayoutParams wrap = new ViewGroup.LayoutParams( 283 ViewGroup.LayoutParams.WRAP_CONTENT, 284 ViewGroup.LayoutParams.WRAP_CONTENT); 285 286 for(int i=0; i<NUM_BEANS; i++) { 287 Bean nv = new Bean(getContext(), null); 288 addView(nv, wrap); 289 nv.z = ((float)i/NUM_BEANS); 290 nv.z *= nv.z; 291 nv.reset(); 292 nv.x = (randfrange(0, boardWidth)); 293 nv.y = (randfrange(0, boardHeight)); 294 } 295 296 if (mAnim != null) { 297 mAnim.cancel(); 298 } 299 mAnim = new TimeAnimator(); 300 mAnim.setTimeListener(new TimeAnimator.TimeListener() { 301 private long lastPrint = 0; 302 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { 303 if (DEBUG && totalTime - lastPrint > 5000) { 304 lastPrint = totalTime; 305 for (int i=0; i<getChildCount(); i++) { 306 android.util.Log.d("BeanBag", "bean " + i + ": " + getChildAt(i)); 307 } 308 } 309 310 for (int i=0; i<getChildCount(); i++) { 311 View v = getChildAt(i); 312 if (!(v instanceof Bean)) continue; 313 Bean nv = (Bean) v; 314 nv.update(deltaTime / 1000f); 315 316 for (int j=i+1; j<getChildCount(); j++) { 317 View v2 = getChildAt(j); 318 if (!(v2 instanceof Bean)) continue; 319 Bean nv2 = (Bean) v2; 320 final float overlap = nv.overlap(nv2); 321 } 322 323 nv.setRotation(nv.a); 324 nv.setX(nv.x-nv.getPivotX()); 325 nv.setY(nv.y-nv.getPivotY()); 326 327 if ( nv.x < - MAX_RADIUS 328 || nv.x > boardWidth + MAX_RADIUS 329 || nv.y < -MAX_RADIUS 330 || nv.y > boardHeight + MAX_RADIUS) 331 { 332 nv.reset(); 333 } 334 } 335 336 if (DEBUG) invalidate(); 337 } 338 }); 339 } 340 341 @Override 342 protected void onSizeChanged (int w, int h, int oldw, int oldh) { 343 super.onSizeChanged(w,h,oldw,oldh); 344 boardWidth = w; 345 boardHeight = h; 346 // android.util.Log.d("Nyandroid", "resized: " + w + "x" + h); 347 } 348 349 public void startAnimation() { 350 stopAnimation(); 351 if (mAnim == null) { 352 post(new Runnable() { public void run() { 353 reset(); 354 startAnimation(); 355 } }); 356 } else { 357 mAnim.start(); 358 } 359 } 360 361 public void stopAnimation() { 362 if (mAnim != null) mAnim.cancel(); 363 } 364 365 @Override 366 protected void onDetachedFromWindow() { 367 super.onDetachedFromWindow(); 368 stopAnimation(); 369 } 370 371 @Override 372 public boolean isOpaque() { 373 return false; 374 } 375 376 @Override 377 public void onDraw(Canvas c) { 378 if (DEBUG) { 379 //android.util.Log.d("BeanBag", "onDraw"); 380 Paint pt = new Paint(); 381 pt.setAntiAlias(true); 382 pt.setStyle(Paint.Style.STROKE); 383 pt.setColor(0xFFFF0000); 384 pt.setStrokeWidth(4.0f); 385 c.drawRect(0, 0, getWidth(), getHeight(), pt); 386 pt.setColor(0xFFFFCC00); 387 pt.setStrokeWidth(1.0f); 388 for (int i=0; i<getChildCount(); i++) { 389 Bean b = (Bean) getChildAt(i); 390 final float a = (360-b.a)/180f*3.14159f; 391 final float tx = b.getTranslationX(); 392 final float ty = b.getTranslationY(); 393 c.drawCircle(b.x, b.y, b.r, pt); 394 c.drawCircle(tx, ty, 4, pt); 395 c.drawLine(b.x, b.y, (float)(b.x+b.r*Math.sin(a)), (float)(b.y+b.r*Math.cos(a)), pt); 396 } 397 } 398 } 399 } 400 401 private Board mBoard; 402 403 @Override 404 public void onStart() { 405 super.onStart(); 406 407 // ACHIEVEMENT UNLOCKED 408 PackageManager pm = getPackageManager(); 409 pm.setComponentEnabledSetting(new ComponentName(this, BeanBagDream.class), 410 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); 411 412 getWindow().addFlags( 413 WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON 414 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 415 ); 416 mBoard = new Board(this, null); 417 setContentView(mBoard); 418 } 419 420 @Override 421 public void onPause() { 422 super.onPause(); 423 mBoard.stopAnimation(); 424 } 425 426 @Override 427 public void onResume() { 428 super.onResume(); 429 mBoard.startAnimation(); 430 } 431 } 432