Rocket Launcher!

This is an Android Dream, which is kind of like an
interactive screensaver implemented as an Activity.

The Rocket Launcher uses the Launcher's own icon cache to
show the user's installed apps in hyperspace. Tap the screen
to slow down to impulse power.  Then, tap an icon to launch.

(depends on I4559a95 for android.dreams in support lib)

Change-Id: Ie6ce34ec1c863667faeada4f39b225f3946e8633
This commit is contained in:
Daniel Sandler 2011-05-12 00:06:32 -04:00 committed by Daniel Sandler
parent 83e3868e90
commit 4e1cd23a51
6 changed files with 475 additions and 1 deletions

View File

@ -21,7 +21,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := android-common
LOCAL_STATIC_JAVA_LIBRARIES := android-common android-support-v13
LOCAL_SRC_FILES := $(call all-subdir-java-files) $(call all-renderscript-files-under, src)

View File

@ -100,6 +100,18 @@
android:resource="@xml/wallpaper_picker_preview" />
</activity>
<activity android:name="com.android.launcher2.RocketLauncher"
android:label="@string/dream_name"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
android:hardwareAccelerated="true"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DREAM" />
</intent-filter>
</activity>
<!-- Intent received used to install shortcuts from other applications -->
<receiver
android:name="com.android.launcher2.InstallShortcutReceiver"

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/homescreen_small_green" />
<item android:drawable="@android:color/transparent" />
</selector>

View File

@ -244,4 +244,7 @@
<!-- Text to inform the user that they can't uninstall a system application -->
<string name="uninstall_system_app_text">This is a system application and cannot be uninstalled.</string>
<!-- Title of the Android Dreams (screensaver) module -->
<string name="dream_name">Rocket Launcher</string>
</resources>

View File

@ -24,6 +24,7 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.Pair;
import android.util.DisplayMetrics;
import java.util.HashMap;
@ -187,4 +188,16 @@ public class IconCache {
}
return entry;
}
public HashMap<ComponentName,Bitmap> getAllIcons() {
synchronized (mCache) {
HashMap<ComponentName,Bitmap> set = new HashMap<ComponentName,Bitmap>();
int i = 0;
for (ComponentName cn : mCache.keySet()) {
final CacheEntry e = mCache.get(cn);
set.put(cn, e.icon);
}
return set;
}
}
}

View File

@ -0,0 +1,426 @@
/*);
* Copyright (C) 2011 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.
*/
// TODO:
// background stellar matter:
// - add some slow horizontal parallax motion, or perhaps veeeeery gradual outward drift
package com.android.launcher2;
import android.animation.AnimatorSet;
import android.animation.PropertyValuesHolder;
import android.animation.ObjectAnimator;
import android.animation.TimeAnimator;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.support.v13.dreams.BasicDream;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Pair;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import java.util.HashMap;
import java.util.Random;
import com.android.launcher.R;
public class RocketLauncher extends BasicDream {
public static final boolean ROCKET_LAUNCHER = true;
public static class Board extends FrameLayout
{
public static final boolean FIXED_STARS = true;
public static final boolean FLYING_STARS = true;
public static final int NUM_ICONS = 20;
public static final float MANEUVERING_THRUST_SCALE = 0.1f; // tenth speed
private boolean mManeuveringThrusters = false;
private float mSpeedScale = 1.0f;
public static final int LAUNCH_ZOOM_TIME = 400; // ms
HashMap<ComponentName, Bitmap> mIcons;
ComponentName[] mComponentNames;
static Random sRNG = new Random();
static float lerp(float a, float b, float f) {
return (b-a)*f + a;
}
static float randfrange(float a, float b) {
return lerp(a, b, sRNG.nextFloat());
}
static int randsign() {
return sRNG.nextBoolean() ? 1 : -1;
}
static <E> E pick(E[] array) {
if (array.length == 0) return null;
return array[sRNG.nextInt(array.length)];
}
public class FlyingIcon extends ImageView {
public static final float VMAX = 1000.0f;
public static final float VMIN = 100.0f;
public static final float ANGULAR_VMAX = 45f;
public static final float ANGULAR_VMIN = 0f;
public static final float SCALE_MIN = 0.5f;
public static final float SCALE_MAX = 4f;
public float v, vr;
public final float[] hsv = new float[3];
public float angle, anglex, angley;
public float fuse;
public float dist;
public float endscale;
public float boardCenterX, boardCenterY;
public ComponentName component;
public FlyingIcon(Context context, AttributeSet as) {
super(context, as);
setLayerType(View.LAYER_TYPE_HARDWARE, null);
setBackgroundResource(R.drawable.flying_icon_bg);
//android.util.Log.d("RocketLauncher", "ctor: " + this);
hsv[1] = 1f;
hsv[2] = 1f;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!mManeuveringThrusters || component == null) {
return false;
}
if (getAlpha() < 0.5f) {
setPressed(false);
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setPressed(true);
Board.this.resetWarpTimer();
break;
case MotionEvent.ACTION_MOVE:
final Rect hit = new Rect();
final Point offset = new Point();
getGlobalVisibleRect(hit, offset);
final int globx = (int) event.getX() + offset.x;
final int globy = (int) event.getY() + offset.y;
setPressed(hit.contains(globx, globy));
Board.this.resetWarpTimer();
break;
case MotionEvent.ACTION_UP:
if (isPressed()) {
setPressed(false);
postDelayed(new Runnable() {
public void run() {
try {
getContext().startActivity(new Intent(Intent.ACTION_MAIN)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setComponent(component));
} catch (android.content.ActivityNotFoundException e) {
} catch (SecurityException e) {
}
}
}, LAUNCH_ZOOM_TIME);
endscale = 0;
AnimatorSet s = new AnimatorSet();
s.playTogether(
ObjectAnimator.ofFloat(this, "scaleX", 15f),
ObjectAnimator.ofFloat(this, "scaleY", 15f),
ObjectAnimator.ofFloat(this, "alpha", 0f)
);
// make sure things are still moving until the very last instant the
// activity is visible
s.setDuration((int)(LAUNCH_ZOOM_TIME * 1.25));
s.setInterpolator(new android.view.animation.AccelerateInterpolator(3));
s.start();
}
break;
}
return true;
}
public String toString() {
return String.format("<'%s' @ (%.1f, %.1f) v=%.1f a=%.1f dist/fuse=%.1f/%.1f>",
"icon", getX(), getY(), v, angle, dist, fuse);
}
public void randomizeIcon() {
component = pick(mComponentNames);
setImageBitmap(mIcons.get(component));
}
public void randomize() {
v = randfrange(VMIN, VMAX);
angle = randfrange(0, 360f);
anglex = (float) Math.sin(angle / 180. * Math.PI);
angley = (float) Math.cos(angle / 180. * Math.PI);
vr = randfrange(ANGULAR_VMIN, ANGULAR_VMAX) * randsign();
endscale = randfrange(SCALE_MIN, SCALE_MAX);
randomizeIcon();
}
public void reset() {
randomize();
boardCenterX = (Board.this.getWidth() - getWidth()) / 2;
boardCenterY = (Board.this.getHeight() - getHeight()) / 2;
setX(boardCenterX);
setY(boardCenterY);
fuse = (float) Math.max(boardCenterX, boardCenterY);
setRotation(180-angle);
setScaleX(0f);
setScaleY(0f);
dist = 0;
setAlpha(0f);
}
public void update(float dt) {
dist += v * dt;
setX(getX() + anglex * v * dt);
setY(getY() + angley * v * dt);
//setRotation(getRotation() + vr * dt);
if (endscale > 0) {
float scale = lerp(0, endscale, (float) Math.sqrt(dist / fuse));
setScaleX(scale * lerp(1f, 0.75f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3)));
setScaleY(scale * lerp(1f, 1.5f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3)));
final float q1 = fuse*0.15f;
final float q4 = fuse*0.75f;
if (dist < q1) {
setAlpha((float) Math.sqrt(dist/q1));
} else if (dist > q4) {
setAlpha((dist >= fuse) ? 0f : (1f-(float)Math.pow((dist-q4)/(fuse-q4),2)));
} else {
setAlpha(1f);
}
}
}
}
public class FlyingStar extends FlyingIcon {
public FlyingStar(Context context, AttributeSet as) {
super(context, as);
}
public void randomizeIcon() {
setImageResource(R.drawable.widget_resize_handle_bottom);
}
public void randomize() {
super.randomize();
v = randfrange(VMAX*0.75f, VMAX*2f); // fasticate
endscale = randfrange(1f, 2f); // ensmallen
}
}
TimeAnimator mAnim;
public Board(Context context, AttributeSet as) {
super(context, as);
setBackgroundColor(0xFF000000);
LauncherApplication app = (LauncherApplication)context.getApplicationContext();
mIcons = app.getIconCache().getAllIcons();
mComponentNames = new ComponentName[mIcons.size()];
mComponentNames = mIcons.keySet().toArray(mComponentNames);
}
private void reset() {
removeAllViews();
final ViewGroup.LayoutParams wrap = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
if (FIXED_STARS) {
for(int i=0; i<20; i++) {
ImageView fixedStar = new ImageView(getContext(), null);
fixedStar.setImageResource(R.drawable.widget_resize_handle_bottom);
final float s = randfrange(0.25f, 0.75f);
fixedStar.setScaleX(s);
fixedStar.setScaleY(s);
fixedStar.setAlpha(0.75f);
addView(fixedStar, wrap);
fixedStar.setX(randfrange(0, getWidth()));
fixedStar.setY(randfrange(0, getHeight()));
}
}
for(int i=0; i<NUM_ICONS*2; i++) {
FlyingIcon nv = (FLYING_STARS && (i < NUM_ICONS))
? new FlyingStar(getContext(), null)
: new FlyingIcon(getContext(), null);
addView(nv, wrap);
nv.reset();
}
mAnim = new TimeAnimator();
mAnim.setTimeListener(new TimeAnimator.TimeListener() {
public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
// setRotation(totalTime * 0.01f); // not as cool as you would think
final int START_ZOOM_TIME = 3000;
if (totalTime < START_ZOOM_TIME) {
final float x = totalTime/(float)START_ZOOM_TIME;
final float s = 1f-(float)Math.pow(x-1, 4);
setScaleX(s); setScaleY(s);
} else {
setScaleX(1.0f); setScaleY(1.0f);
}
if (mManeuveringThrusters) {
if (mSpeedScale > MANEUVERING_THRUST_SCALE) {
mSpeedScale -= (2*deltaTime/1000f);
}
if (mSpeedScale < MANEUVERING_THRUST_SCALE) {
mSpeedScale = MANEUVERING_THRUST_SCALE;
}
} else {
if (mSpeedScale < 1.0f) {
mSpeedScale += (deltaTime/1000f);
}
if (mSpeedScale > 1.0f) {
mSpeedScale = 1.0f;
}
}
for (int i=0; i<getChildCount(); i++) {
View v = getChildAt(i);
if (!(v instanceof FlyingIcon)) continue;
FlyingIcon nv = (FlyingIcon) v;
nv.update(deltaTime / 1000f * mSpeedScale);
final float scaledWidth = nv.getWidth() * nv.getScaleX();
final float scaledHeight = nv.getHeight() * nv.getScaleY();
if ( nv.getX() + scaledWidth < 0
|| nv.getX() - scaledWidth > getWidth()
|| nv.getY() + scaledHeight < 0
|| nv.getY() - scaledHeight > getHeight())
{
nv.reset();
}
}
}
});
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setLayerType(View.LAYER_TYPE_HARDWARE, null);
setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
reset();
mAnim.start();
}
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
super.onSizeChanged(w,h,oldw,oldh);
mAnim.cancel();
reset();
mAnim.start();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mAnim.cancel();
}
@Override
public boolean isOpaque() {
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
// we want to eat touch events ourselves if we're in warp speed
return (!(ROCKET_LAUNCHER && mManeuveringThrusters));
}
final Runnable mEngageWarp = new Runnable() {
@Override
public void run() {
mManeuveringThrusters = false;
}
};
public void resetWarpTimer() {
final Handler h = getHandler();
h.removeCallbacks(mEngageWarp);
h.postDelayed(mEngageWarp, 5000);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!ROCKET_LAUNCHER) {
return true;
}
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (!mManeuveringThrusters) {
mManeuveringThrusters = true;
resetWarpTimer();
return true;
}
}
return false;
}
}
@Override
public void onStart() {
super.onStart();
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
final int longside = metrics.widthPixels > metrics.heightPixels
? metrics.widthPixels : metrics.heightPixels;
Board b = new Board(this, null);
setContentView(b, new ViewGroup.LayoutParams(longside, longside));
b.setX((metrics.widthPixels - longside) / 2);
b.setY((metrics.heightPixels - longside) / 2);
}
@Override
public void onUserInteraction() {
if (!ROCKET_LAUNCHER) {
finish();
}
}
}