1 /*); 2 * Copyright (C) 2011 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 // TODO: 18 // background stellar matter: 19 // - add some slow horizontal parallax motion, or perhaps veeeeery gradual outward drift 20 21 package com.android.launcher2; 22 23 import android.animation.AnimatorSet; 24 import android.animation.ObjectAnimator; 25 import android.animation.TimeAnimator; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.graphics.Bitmap; 30 import android.graphics.Point; 31 import android.graphics.Rect; 32 import android.os.Handler; 33 import android.support.v13.dreams.BasicDream; 34 import android.util.AttributeSet; 35 import android.util.DisplayMetrics; 36 import android.view.MotionEvent; 37 import android.view.View; 38 import android.view.ViewGroup; 39 import android.widget.FrameLayout; 40 import android.widget.ImageView; 41 42 import com.android.launcher.R; 43 44 import java.util.HashMap; 45 import java.util.Random; 46 47 public class RocketLauncher extends BasicDream { 48 public static final boolean ROCKET_LAUNCHER = true; 49 50 public static class Board extends FrameLayout 51 { 52 public static final boolean FIXED_STARS = true; 53 public static final boolean FLYING_STARS = true; 54 public static final int NUM_ICONS = 20; 55 56 public static final float MANEUVERING_THRUST_SCALE = 0.1f; // tenth speed 57 private boolean mManeuveringThrusters = false; 58 private float mSpeedScale = 1.0f; 59 60 public static final int LAUNCH_ZOOM_TIME = 400; // ms 61 62 HashMap<ComponentName, Bitmap> mIcons; 63 ComponentName[] mComponentNames; 64 65 static Random sRNG = new Random(); 66 lerp(float a, float b, float f)67 static float lerp(float a, float b, float f) { 68 return (b-a)*f + a; 69 } 70 randfrange(float a, float b)71 static float randfrange(float a, float b) { 72 return lerp(a, b, sRNG.nextFloat()); 73 } 74 randsign()75 static int randsign() { 76 return sRNG.nextBoolean() ? 1 : -1; 77 } 78 pick(E[] array)79 static <E> E pick(E[] array) { 80 if (array.length == 0) return null; 81 return array[sRNG.nextInt(array.length)]; 82 } 83 84 public class FlyingIcon extends ImageView { 85 public static final float VMAX = 1000.0f; 86 public static final float VMIN = 100.0f; 87 public static final float ANGULAR_VMAX = 45f; 88 public static final float ANGULAR_VMIN = 0f; 89 public static final float SCALE_MIN = 0.5f; 90 public static final float SCALE_MAX = 4f; 91 92 public float v, vr; 93 94 public final float[] hsv = new float[3]; 95 96 public float angle, anglex, angley; 97 public float fuse; 98 public float dist; 99 public float endscale; 100 public float boardCenterX, boardCenterY; 101 102 public ComponentName component; 103 FlyingIcon(Context context, AttributeSet as)104 public FlyingIcon(Context context, AttributeSet as) { 105 super(context, as); 106 setLayerType(View.LAYER_TYPE_HARDWARE, null); 107 108 setBackgroundResource(R.drawable.flying_icon_bg); 109 //android.util.Log.d("RocketLauncher", "ctor: " + this); 110 hsv[1] = 1f; 111 hsv[2] = 1f; 112 } 113 114 @Override onTouchEvent(MotionEvent event)115 public boolean onTouchEvent(MotionEvent event) { 116 if (!mManeuveringThrusters || component == null) { 117 return false; 118 } 119 if (getAlpha() < 0.5f) { 120 setPressed(false); 121 return false; 122 } 123 124 switch (event.getAction()) { 125 case MotionEvent.ACTION_DOWN: 126 setPressed(true); 127 Board.this.resetWarpTimer(); 128 break; 129 case MotionEvent.ACTION_MOVE: 130 final Rect hit = new Rect(); 131 final Point offset = new Point(); 132 getGlobalVisibleRect(hit, offset); 133 final int globx = (int) event.getX() + offset.x; 134 final int globy = (int) event.getY() + offset.y; 135 setPressed(hit.contains(globx, globy)); 136 Board.this.resetWarpTimer(); 137 break; 138 case MotionEvent.ACTION_UP: 139 if (isPressed()) { 140 setPressed(false); 141 postDelayed(new Runnable() { 142 public void run() { 143 try { 144 getContext().startActivity(new Intent(Intent.ACTION_MAIN) 145 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 146 .setComponent(component)); 147 } catch (android.content.ActivityNotFoundException e) { 148 } catch (SecurityException e) { 149 } 150 } 151 }, LAUNCH_ZOOM_TIME); 152 endscale = 0; 153 AnimatorSet s = new AnimatorSet(); 154 s.playTogether( 155 ObjectAnimator.ofFloat(this, "scaleX", 15f), 156 ObjectAnimator.ofFloat(this, "scaleY", 15f), 157 ObjectAnimator.ofFloat(this, "alpha", 0f) 158 ); 159 160 // make sure things are still moving until the very last instant the 161 // activity is visible 162 s.setDuration((int)(LAUNCH_ZOOM_TIME * 1.25)); 163 s.setInterpolator(new android.view.animation.AccelerateInterpolator(3)); 164 s.start(); 165 } 166 break; 167 } 168 return true; 169 } 170 toString()171 public String toString() { 172 return String.format("<'%s' @ (%.1f, %.1f) v=%.1f a=%.1f dist/fuse=%.1f/%.1f>", 173 "icon", getX(), getY(), v, angle, dist, fuse); 174 } 175 randomizeIcon()176 public void randomizeIcon() { 177 component = pick(mComponentNames); 178 setImageBitmap(mIcons.get(component)); 179 } 180 randomize()181 public void randomize() { 182 v = randfrange(VMIN, VMAX); 183 angle = randfrange(0, 360f); 184 anglex = (float) Math.sin(angle / 180. * Math.PI); 185 angley = (float) Math.cos(angle / 180. * Math.PI); 186 vr = randfrange(ANGULAR_VMIN, ANGULAR_VMAX) * randsign(); 187 endscale = randfrange(SCALE_MIN, SCALE_MAX); 188 189 randomizeIcon(); 190 } reset()191 public void reset() { 192 randomize(); 193 boardCenterX = (Board.this.getWidth() - getWidth()) / 2; 194 boardCenterY = (Board.this.getHeight() - getHeight()) / 2; 195 setX(boardCenterX); 196 setY(boardCenterY); 197 fuse = (float) Math.max(boardCenterX, boardCenterY); 198 setRotation(180-angle); 199 setScaleX(0f); 200 setScaleY(0f); 201 dist = 0; 202 setAlpha(0f); 203 } update(float dt)204 public void update(float dt) { 205 dist += v * dt; 206 setX(getX() + anglex * v * dt); 207 setY(getY() + angley * v * dt); 208 //setRotation(getRotation() + vr * dt); 209 if (endscale > 0) { 210 float scale = lerp(0, endscale, (float) Math.sqrt(dist / fuse)); 211 setScaleX(scale * lerp(1f, 0.75f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3))); 212 setScaleY(scale * lerp(1f, 1.5f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3))); 213 final float q1 = fuse*0.15f; 214 final float q4 = fuse*0.75f; 215 if (dist < q1) { 216 setAlpha((float) Math.sqrt(dist/q1)); 217 } else if (dist > q4) { 218 setAlpha((dist >= fuse) ? 0f : (1f-(float)Math.pow((dist-q4)/(fuse-q4),2))); 219 } else { 220 setAlpha(1f); 221 } 222 } 223 } 224 } 225 226 public class FlyingStar extends FlyingIcon { FlyingStar(Context context, AttributeSet as)227 public FlyingStar(Context context, AttributeSet as) { 228 super(context, as); 229 } randomizeIcon()230 public void randomizeIcon() { 231 setImageResource(R.drawable.widget_resize_handle_bottom); 232 } randomize()233 public void randomize() { 234 super.randomize(); 235 v = randfrange(VMAX*0.75f, VMAX*2f); // fasticate 236 endscale = randfrange(1f, 2f); // ensmallen 237 } 238 } 239 240 TimeAnimator mAnim; 241 Board(Context context, AttributeSet as)242 public Board(Context context, AttributeSet as) { 243 super(context, as); 244 245 setBackgroundColor(0xFF000000); 246 247 LauncherApplication app = (LauncherApplication)context.getApplicationContext(); 248 mIcons = app.getIconCache().getAllIcons(); 249 mComponentNames = new ComponentName[mIcons.size()]; 250 mComponentNames = mIcons.keySet().toArray(mComponentNames); 251 } 252 reset()253 private void reset() { 254 removeAllViews(); 255 256 final ViewGroup.LayoutParams wrap = new ViewGroup.LayoutParams( 257 ViewGroup.LayoutParams.WRAP_CONTENT, 258 ViewGroup.LayoutParams.WRAP_CONTENT); 259 260 if (FIXED_STARS) { 261 for(int i=0; i<20; i++) { 262 ImageView fixedStar = new ImageView(getContext(), null); 263 fixedStar.setImageResource(R.drawable.widget_resize_handle_bottom); 264 final float s = randfrange(0.25f, 0.75f); 265 fixedStar.setScaleX(s); 266 fixedStar.setScaleY(s); 267 fixedStar.setAlpha(0.75f); 268 addView(fixedStar, wrap); 269 fixedStar.setX(randfrange(0, getWidth())); 270 fixedStar.setY(randfrange(0, getHeight())); 271 } 272 } 273 274 for(int i=0; i<NUM_ICONS*2; i++) { 275 FlyingIcon nv = (FLYING_STARS && (i < NUM_ICONS)) 276 ? new FlyingStar(getContext(), null) 277 : new FlyingIcon(getContext(), null); 278 addView(nv, wrap); 279 nv.reset(); 280 } 281 282 mAnim = new TimeAnimator(); 283 mAnim.setTimeListener(new TimeAnimator.TimeListener() { 284 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { 285 // setRotation(totalTime * 0.01f); // not as cool as you would think 286 287 final int START_ZOOM_TIME = 3000; 288 if (totalTime < START_ZOOM_TIME) { 289 final float x = totalTime/(float)START_ZOOM_TIME; 290 final float s = 1f-(float)Math.pow(x-1, 4); 291 setScaleX(s); setScaleY(s); 292 } else { 293 setScaleX(1.0f); setScaleY(1.0f); 294 } 295 296 if (mManeuveringThrusters) { 297 if (mSpeedScale > MANEUVERING_THRUST_SCALE) { 298 mSpeedScale -= (2*deltaTime/1000f); 299 } 300 if (mSpeedScale < MANEUVERING_THRUST_SCALE) { 301 mSpeedScale = MANEUVERING_THRUST_SCALE; 302 } 303 } else { 304 if (mSpeedScale < 1.0f) { 305 mSpeedScale += (deltaTime/1000f); 306 } 307 if (mSpeedScale > 1.0f) { 308 mSpeedScale = 1.0f; 309 } 310 } 311 312 for (int i=0; i<getChildCount(); i++) { 313 View v = getChildAt(i); 314 if (!(v instanceof FlyingIcon)) continue; 315 FlyingIcon nv = (FlyingIcon) v; 316 nv.update(deltaTime / 1000f * mSpeedScale); 317 final float scaledWidth = nv.getWidth() * nv.getScaleX(); 318 final float scaledHeight = nv.getHeight() * nv.getScaleY(); 319 if ( nv.getX() + scaledWidth < 0 320 || nv.getX() - scaledWidth > getWidth() 321 || nv.getY() + scaledHeight < 0 322 || nv.getY() - scaledHeight > getHeight()) 323 { 324 nv.reset(); 325 } 326 } 327 } 328 }); 329 } 330 331 @Override onAttachedToWindow()332 protected void onAttachedToWindow() { 333 super.onAttachedToWindow(); 334 setLayerType(View.LAYER_TYPE_HARDWARE, null); 335 setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); 336 337 reset(); 338 mAnim.start(); 339 } 340 onSizeChanged(int w, int h, int oldw, int oldh)341 protected void onSizeChanged (int w, int h, int oldw, int oldh) { 342 super.onSizeChanged(w,h,oldw,oldh); 343 mAnim.cancel(); 344 reset(); 345 mAnim.start(); 346 } 347 348 349 @Override onDetachedFromWindow()350 protected void onDetachedFromWindow() { 351 super.onDetachedFromWindow(); 352 mAnim.cancel(); 353 } 354 355 @Override isOpaque()356 public boolean isOpaque() { 357 return true; 358 } 359 360 @Override onInterceptTouchEvent(MotionEvent e)361 public boolean onInterceptTouchEvent(MotionEvent e) { 362 // we want to eat touch events ourselves if we're in warp speed 363 return (!(ROCKET_LAUNCHER && mManeuveringThrusters)); 364 } 365 366 final Runnable mEngageWarp = new Runnable() { 367 @Override 368 public void run() { 369 mManeuveringThrusters = false; 370 } 371 }; resetWarpTimer()372 public void resetWarpTimer() { 373 final Handler h = getHandler(); 374 h.removeCallbacks(mEngageWarp); 375 h.postDelayed(mEngageWarp, 5000); 376 } 377 378 @Override onTouchEvent(MotionEvent event)379 public boolean onTouchEvent(MotionEvent event) { 380 if (!ROCKET_LAUNCHER) { 381 return true; 382 } 383 384 if (event.getAction() == MotionEvent.ACTION_DOWN) { 385 if (!mManeuveringThrusters) { 386 mManeuveringThrusters = true; 387 resetWarpTimer(); 388 return true; 389 } 390 } 391 392 return false; 393 } 394 } 395 396 @Override onStart()397 public void onStart() { 398 super.onStart(); 399 400 DisplayMetrics metrics = new DisplayMetrics(); 401 getWindowManager().getDefaultDisplay().getMetrics(metrics); 402 final int longside = metrics.widthPixels > metrics.heightPixels 403 ? metrics.widthPixels : metrics.heightPixels; 404 405 Board b = new Board(this, null); 406 setContentView(b, new ViewGroup.LayoutParams(longside, longside)); 407 b.setX((metrics.widthPixels - longside) / 2); 408 b.setY((metrics.heightPixels - longside) / 2); 409 } 410 411 @Override onUserInteraction()412 public void onUserInteraction() { 413 if (!ROCKET_LAUNCHER) { 414 finish(); 415 } 416 } 417 } 418