summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDan Bornstein <danfuzz@android.com>2010-11-04 18:26:39 -0700
committerAndroid Git Automerger <android-git-automerger@android.com>2010-11-04 18:26:39 -0700
commitec30827e734ba9c3d2b8a27f3b5224412d1cf2f7 (patch)
treea0b88663607df39ebea5e2fc9722afea51e2b677 /src
parentd9171379e8ac8303ffdaeac8c8c036a7a852f022 (diff)
parent8ba148fb75ecaaf8ad51c271a7891811aab28979 (diff)
downloadandroid_packages_apps_Trebuchet-ec30827e734ba9c3d2b8a27f3b5224412d1cf2f7.tar.gz
android_packages_apps_Trebuchet-ec30827e734ba9c3d2b8a27f3b5224412d1cf2f7.tar.bz2
android_packages_apps_Trebuchet-ec30827e734ba9c3d2b8a27f3b5224412d1cf2f7.zip
Merge branch 'master' of ssh://android-git:29418/platform/packages/apps/Launcher2
Diffstat (limited to 'src')
-rw-r--r--src/com/android/launcher2/AllApps2D.java85
-rw-r--r--src/com/android/launcher2/AllApps3D.java470
-rw-r--r--src/com/android/launcher2/AllAppsList.java13
-rw-r--r--src/com/android/launcher2/AllAppsPagedView.java572
-rw-r--r--src/com/android/launcher2/AllAppsTabbed.java171
-rw-r--r--src/com/android/launcher2/AllAppsView.java2
-rw-r--r--src/com/android/launcher2/ApplicationInfo.java36
-rw-r--r--src/com/android/launcher2/ApplicationInfoDropTarget.java161
-rw-r--r--src/com/android/launcher2/BubbleTextView.java6
-rw-r--r--src/com/android/launcher2/CellLayout.java1652
-rw-r--r--src/com/android/launcher2/CustomizePagedView.java795
-rw-r--r--src/com/android/launcher2/DeferredHandler.java5
-rw-r--r--src/com/android/launcher2/DeleteZone.java126
-rw-r--r--src/com/android/launcher2/Dimmable.java6
-rw-r--r--src/com/android/launcher2/DimmableAppWidgetHostView.java115
-rw-r--r--src/com/android/launcher2/DimmableBubbleTextView.java98
-rw-r--r--src/com/android/launcher2/DragController.java278
-rw-r--r--src/com/android/launcher2/DragLayer.java6
-rw-r--r--src/com/android/launcher2/DragScroller.java12
-rw-r--r--src/com/android/launcher2/DragView.java65
-rw-r--r--src/com/android/launcher2/DropTarget.java40
-rw-r--r--src/com/android/launcher2/FastBitmapDrawable.java5
-rw-r--r--src/com/android/launcher2/Folder.java6
-rw-r--r--src/com/android/launcher2/FolderIcon.java27
-rw-r--r--src/com/android/launcher2/HolographicOutlineHelper.java186
-rw-r--r--src/com/android/launcher2/IconCache.java41
-rw-r--r--src/com/android/launcher2/InstallShortcutReceiver.java55
-rw-r--r--src/com/android/launcher2/InstallWidgetReceiver.java195
-rw-r--r--src/com/android/launcher2/InterruptibleInOutAnimator.java132
-rw-r--r--src/com/android/launcher2/ItemInfo.java5
-rw-r--r--src/com/android/launcher2/Launcher.java1351
-rw-r--r--src/com/android/launcher2/LauncherAnimatorListenerAdapter.java70
-rw-r--r--src/com/android/launcher2/LauncherAppWidgetHost.java2
-rw-r--r--src/com/android/launcher2/LauncherAppWidgetInfo.java30
-rw-r--r--src/com/android/launcher2/LauncherApplication.java21
-rw-r--r--src/com/android/launcher2/LauncherModel.java277
-rw-r--r--src/com/android/launcher2/LiveFolderInfo.java1
-rw-r--r--src/com/android/launcher2/PagedView.java1272
-rw-r--r--src/com/android/launcher2/PagedViewCellLayout.java460
-rw-r--r--src/com/android/launcher2/PagedViewIcon.java245
-rw-r--r--src/com/android/launcher2/PagedViewWidgetIcon.java156
-rw-r--r--src/com/android/launcher2/PagedViewWidgetLayout.java71
-rw-r--r--src/com/android/launcher2/PendingAddItemInfo.java52
-rw-r--r--src/com/android/launcher2/ShortcutInfo.java6
-rw-r--r--src/com/android/launcher2/ShortcutsAdapter.java5
-rw-r--r--src/com/android/launcher2/SmoothPagedView.java193
-rw-r--r--src/com/android/launcher2/SymmetricalLinearTween.java2
-rw-r--r--src/com/android/launcher2/UserFolder.java17
-rw-r--r--src/com/android/launcher2/Utilities.java15
-rw-r--r--src/com/android/launcher2/Workspace.java2391
-rw-r--r--src/com/android/launcher2/allapps.rs389
51 files changed, 10014 insertions, 2378 deletions
diff --git a/src/com/android/launcher2/AllApps2D.java b/src/com/android/launcher2/AllApps2D.java
index 7ad5e4915..9764f2338 100644
--- a/src/com/android/launcher2/AllApps2D.java
+++ b/src/com/android/launcher2/AllApps2D.java
@@ -16,32 +16,30 @@
package com.android.launcher2;
+import com.android.launcher.R;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.Bitmap;
-import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
-import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
-import android.view.ViewConfiguration;
import android.widget.AdapterView;
-import android.widget.ImageButton;
-import android.widget.TextView;
import android.widget.ArrayAdapter;
import android.widget.GridView;
+import android.widget.ImageButton;
import android.widget.RelativeLayout;
+import android.widget.TextView;
import java.util.ArrayList;
import java.util.Collections;
-import com.android.launcher.R;
-
public class AllApps2D
extends RelativeLayout
implements AllAppsView,
@@ -58,8 +56,16 @@ public class AllApps2D
private GridView mGrid;
+ /** All applications in the system (we might only be showing a subset) */
private ArrayList<ApplicationInfo> mAllAppsList = new ArrayList<ApplicationInfo>();
+ /** Currently visible applications in the grid */
+ private ArrayList<ApplicationInfo> mVisibleAppsList = new ArrayList<ApplicationInfo>();
+
+ public enum AppType { APP, GAME, DOWNLOADED, ALL };
+
+ private AppType mCurrentFilter = AppType.ALL;
+
// preserve compatibility with 3D all apps:
// 0.0 -> hidden
// 1.0 -> shown and opaque
@@ -120,30 +126,27 @@ public class AllApps2D
setVisibility(View.GONE);
setSoundEffectsEnabled(false);
- mAppsAdapter = new AppsAdapter(getContext(), mAllAppsList);
- mAppsAdapter.setNotifyOnChange(false);
+ mAppsAdapter = new AppsAdapter(getContext(), mVisibleAppsList);
}
@Override
protected void onFinishInflate() {
- setBackgroundColor(Color.BLACK);
-
try {
mGrid = (GridView)findViewWithTag("all_apps_2d_grid");
if (mGrid == null) throw new Resources.NotFoundException();
mGrid.setOnItemClickListener(this);
mGrid.setOnItemLongClickListener(this);
- mGrid.setBackgroundColor(Color.BLACK);
- mGrid.setCacheColorHint(Color.BLACK);
+ // The home button is optional; some layouts might not use it
ImageButton homeButton = (ImageButton) findViewWithTag("all_apps_2d_home");
- if (homeButton == null) throw new Resources.NotFoundException();
- homeButton.setOnClickListener(
- new View.OnClickListener() {
- public void onClick(View v) {
- mLauncher.closeAllApps(true);
- }
- });
+ if (homeButton != null) {
+ homeButton.setOnClickListener(
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ mLauncher.closeAllApps(true);
+ }
+ });
+ }
} catch (Resources.NotFoundException e) {
Log.e(TAG, "Can't find necessary layout elements for AllApps2D");
}
@@ -250,19 +253,17 @@ public class AllApps2D
return mZoom > 0.001f;
}
- @Override
- public boolean isOpaque() {
- return mZoom > 0.999f;
+ public boolean isAnimating() {
+ return (getAnimation() != null);
}
public void setApps(ArrayList<ApplicationInfo> list) {
mAllAppsList.clear();
addApps(list);
+ filterApps(mCurrentFilter);
}
public void addApps(ArrayList<ApplicationInfo> list) {
-// Log.d(TAG, "addApps: " + list.size() + " apps: " + list.toString());
-
final int N = list.size();
for (int i=0; i<N; i++) {
@@ -274,11 +275,12 @@ public class AllApps2D
}
mAllAppsList.add(index, item);
}
- mAppsAdapter.notifyDataSetChanged();
+ filterApps(mCurrentFilter);
}
public void removeApps(ArrayList<ApplicationInfo> list) {
final int N = list.size();
+
for (int i=0; i<N; i++) {
final ApplicationInfo item = list.get(i);
int index = findAppByComponent(mAllAppsList, item);
@@ -289,7 +291,7 @@ public class AllApps2D
// Try to recover. This should keep us from crashing for now.
}
}
- mAppsAdapter.notifyDataSetChanged();
+ filterApps(mCurrentFilter);
}
public void updateApps(ArrayList<ApplicationInfo> list) {
@@ -298,6 +300,33 @@ public class AllApps2D
addApps(list);
}
+ public void filterApps(AppType appType) {
+ mCurrentFilter = appType;
+
+ mAppsAdapter.setNotifyOnChange(false);
+ mVisibleAppsList.clear();
+ if (appType == AppType.ALL) {
+ mVisibleAppsList.addAll(mAllAppsList);
+ } else {
+ int searchFlags = 0;
+
+ if (appType == AppType.APP) {
+ searchFlags = ApplicationInfo.APP_FLAG;
+ } else if (appType == AppType.GAME) {
+ searchFlags = ApplicationInfo.GAME_FLAG;
+ } else if (appType == AppType.DOWNLOADED) {
+ searchFlags = ApplicationInfo.DOWNLOADED_FLAG;
+ }
+
+ for (ApplicationInfo info : mAllAppsList) {
+ if ((info.flags & searchFlags) != 0) {
+ mVisibleAppsList.add(info);
+ }
+ }
+ }
+ mAppsAdapter.notifyDataSetChanged();
+ }
+
private static int findAppByComponent(ArrayList<ApplicationInfo> list, ApplicationInfo item) {
ComponentName component = item.intent.getComponent();
final int N = list.size();
diff --git a/src/com/android/launcher2/AllApps3D.java b/src/com/android/launcher2/AllApps3D.java
index 308ad28d9..b56a9c9fa 100644
--- a/src/com/android/launcher2/AllApps3D.java
+++ b/src/com/android/launcher2/AllApps3D.java
@@ -16,6 +16,12 @@
package com.android.launcher2;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+import com.android.launcher.R;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
@@ -23,19 +29,7 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.Rect;
-import android.renderscript.Allocation;
-import android.renderscript.Element;
-import android.renderscript.ProgramFragment;
-import android.renderscript.ProgramStore;
-import android.renderscript.ProgramVertex;
-import android.renderscript.RSSurfaceView;
-import android.renderscript.RenderScriptGL;
-import android.renderscript.RenderScript;
-import android.renderscript.Sampler;
-import android.renderscript.Script;
-import android.renderscript.ScriptC;
-import android.renderscript.SimpleMesh;
-import android.renderscript.Type;
+import android.renderscript.*;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -48,12 +42,6 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-
-import com.android.launcher.R;
-
public class AllApps3D extends RSSurfaceView
implements AllAppsView, View.OnClickListener, View.OnLongClickListener, DragSource {
private static final String TAG = "Launcher.AllApps3D";
@@ -131,27 +119,16 @@ public class AllApps3D extends RSSurfaceView
private boolean mSurrendered;
private int mRestoreFocusIndex = -1;
-
+
@SuppressWarnings({"UnusedDeclaration"})
static class Defines {
- public static final int ALLOC_PARAMS = 0;
- public static final int ALLOC_STATE = 1;
- public static final int ALLOC_ICON_IDS = 3;
- public static final int ALLOC_LABEL_IDS = 4;
- public static final int ALLOC_VP_CONSTANTS = 5;
-
public static final int COLUMNS_PER_PAGE_PORTRAIT = 4;
public static final int ROWS_PER_PAGE_PORTRAIT = 4;
public static final int COLUMNS_PER_PAGE_LANDSCAPE = 6;
public static final int ROWS_PER_PAGE_LANDSCAPE = 3;
- public static final int ICON_WIDTH_PX = 64;
- public static final int ICON_TEXTURE_WIDTH_PX = 74;
public static final int SELECTION_TEXTURE_WIDTH_PX = 74 + 20;
-
- public static final int ICON_HEIGHT_PX = 64;
- public static final int ICON_TEXTURE_HEIGHT_PX = 74;
public static final int SELECTION_TEXTURE_HEIGHT_PX = 74 + 20;
}
@@ -159,7 +136,6 @@ public class AllApps3D extends RSSurfaceView
super(context, attrs);
setFocusable(true);
setSoundEffectsEnabled(false);
- getHolder().setFormat(PixelFormat.TRANSLUCENT);
final ViewConfiguration config = ViewConfiguration.get(context);
mSlop = config.getScaledTouchSlop();
mMaxFlingVelocity = config.getScaledMaximumFlingVelocity();
@@ -170,7 +146,10 @@ public class AllApps3D extends RSSurfaceView
getHolder().setFormat(PixelFormat.TRANSLUCENT);
if (sRS == null) {
- sRS = createRenderScript(true);
+ RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
+ sc.setDepth(16, 16);
+ sc.setAlpha(8, 8);
+ sRS = createRenderScript(sc);
} else {
createRenderScript(sRS);
}
@@ -274,26 +253,11 @@ public class AllApps3D extends RSSurfaceView
sRollo.dirtyCheck();
sRollo.resize(w, h);
+ Log.d(TAG, "sc " + sRS);
if (sRS != null) {
sRS.mMessageCallback = mMessageProc = new AAMessage();
}
- if (sRollo.mUniformAlloc != null) {
- float tf[] = new float[] {72.f, 72.f,
- 120.f, 120.f, 0.f, 0.f,
- 120.f, 680.f,
- (2.f / 480.f), 0, -((float)w / 2) - 0.25f, -380.25f};
- if (w > h) {
- tf[6] = 40.f;
- tf[7] = h - 40.f;
- tf[9] = 1.f;
- tf[10] = -((float)w / 2) - 0.25f;
- tf[11] = -((float)h / 2) - 0.25f;
- }
-
- sRollo.mUniformAlloc.data(tf);
- }
-
//long endTime = SystemClock.uptimeMillis();
//Log.d(TAG, "surfaceChanged took " + (endTime-startTime) + "ms");
}
@@ -307,18 +271,17 @@ public class AllApps3D extends RSSurfaceView
if (mArrowNavigation) {
if (!hasWindowFocus) {
// Clear selection when we lose window focus
- mLastSelectedIcon = sRollo.mState.selectedIconIndex;
+ mLastSelectedIcon = sRollo.mScript.get_gSelectedIconIndex();
sRollo.setHomeSelected(SELECTED_NONE);
sRollo.clearSelectedIcon();
- sRollo.mState.save();
} else {
- if (sRollo.mState.iconCount > 0) {
+ if (sRollo.mScript.get_gIconCount() > 0) {
if (mLastSelection == SELECTION_ICONS) {
int selection = mLastSelectedIcon;
final int firstIcon = Math.round(sRollo.mScrollPos) * mColumnsPerPage;
if (selection < 0 || // No selection
selection < firstIcon || // off the top of the screen
- selection >= sRollo.mState.iconCount || // past last icon
+ selection >= sRollo.mScript.get_gIconCount() || // past last icon
selection >= firstIcon + // past last icon on screen
(mColumnsPerPage * mRowsPerPage)) {
selection = firstIcon;
@@ -326,10 +289,8 @@ public class AllApps3D extends RSSurfaceView
// Select the first icon when we gain window focus
sRollo.selectIcon(selection, SELECTED_FOCUSED);
- sRollo.mState.save();
} else if (mLastSelection == SELECTION_HOME) {
sRollo.setHomeSelected(SELECTED_FOCUSED);
- sRollo.mState.save();
}
}
}
@@ -356,7 +317,6 @@ public class AllApps3D extends RSSurfaceView
// Clear selection when we lose focus
sRollo.clearSelectedIcon();
sRollo.setHomeSelected(SELECTED_NONE);
- sRollo.mState.save();
mArrowNavigation = false;
}
} else {
@@ -366,11 +326,10 @@ public class AllApps3D extends RSSurfaceView
}
private void gainFocus() {
- if (!mArrowNavigation && sRollo.mState.iconCount > 0) {
+ if (!mArrowNavigation && sRollo.mScript.get_gIconCount() > 0) {
// Select the first icon when we gain keyboard focus
mArrowNavigation = true;
sRollo.selectIcon(Math.round(sRollo.mScrollPos) * mColumnsPerPage, SELECTED_FOCUSED);
- sRollo.mState.save();
}
}
@@ -382,7 +341,7 @@ public class AllApps3D extends RSSurfaceView
if (!isVisible()) {
return false;
}
- final int iconCount = sRollo.mState.iconCount;
+ final int iconCount = sRollo.mScript.get_gIconCount();
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
if (mArrowNavigation) {
@@ -390,7 +349,7 @@ public class AllApps3D extends RSSurfaceView
reallyPlaySoundEffect(SoundEffectConstants.CLICK);
mLauncher.closeAllApps(true);
} else {
- int whichApp = sRollo.mState.selectedIconIndex;
+ int whichApp = sRollo.mScript.get_gSelectedIconIndex();
if (whichApp >= 0) {
ApplicationInfo app = mAllAppsList.get(whichApp);
mLauncher.startActivitySafely(app.intent, app);
@@ -402,10 +361,10 @@ public class AllApps3D extends RSSurfaceView
if (iconCount > 0) {
final boolean isPortrait = getWidth() < getHeight();
-
+
mArrowNavigation = true;
- int currentSelection = sRollo.mState.selectedIconIndex;
+ int currentSelection = sRollo.mScript.get_gSelectedIconIndex();
int currentTopRow = Math.round(sRollo.mScrollPos);
// The column of the current selection, in the range 0..COLUMNS_PER_PAGE_PORTRAIT-1
@@ -511,7 +470,6 @@ public class AllApps3D extends RSSurfaceView
}
if (newSelection != currentSelection) {
sRollo.selectIcon(newSelection, SELECTED_FOCUSED);
- sRollo.mState.save();
}
}
return handled;
@@ -611,7 +569,6 @@ public class AllApps3D extends RSSurfaceView
(!isPortrait && x > mTouchXBorders[mTouchXBorders.length-1])) {
mTouchTracking = TRACKING_HOME;
sRollo.setHomeSelected(SELECTED_PRESSED);
- sRollo.mState.save();
mCurrentIconIndex = -1;
} else {
mTouchTracking = TRACKING_FLING;
@@ -619,9 +576,6 @@ public class AllApps3D extends RSSurfaceView
mMotionDownRawX = (int)ev.getRawX();
mMotionDownRawY = (int)ev.getRawY();
- sRollo.mState.newPositionX = ev.getRawY() / getHeight();
- sRollo.mState.newTouchDown = 1;
-
if (!sRollo.checkClickOK()) {
sRollo.clearSelectedIcon();
} else {
@@ -632,8 +586,7 @@ public class AllApps3D extends RSSurfaceView
cancelLongPress();
}
}
- sRollo.mState.save();
- sRollo.move();
+ sRollo.move(ev.getRawY() / getHeight());
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(ev);
mStartedScrolling = false;
@@ -646,7 +599,6 @@ public class AllApps3D extends RSSurfaceView
y > mTouchYBorders[mTouchYBorders.length-1]) || (!isPortrait
&& x > mTouchXBorders[mTouchXBorders.length-1])
? SELECTED_PRESSED : SELECTED_NONE);
- sRollo.mState.save();
} else if (mTouchTracking == TRACKING_FLING) {
int rawY = (int)ev.getRawY();
int slop;
@@ -667,14 +619,11 @@ public class AllApps3D extends RSSurfaceView
cancelLongPress();
mCurrentIconIndex = -1;
}
- sRollo.mState.newPositionX = ev.getRawY() / getHeight();
- sRollo.mState.newTouchDown = 1;
- sRollo.move();
+ sRollo.move(ev.getRawY() / getHeight());
mStartedScrolling = true;
sRollo.clearSelectedIcon();
mVelocityTracker.addMovement(ev);
- sRollo.mState.save();
}
}
break;
@@ -688,18 +637,13 @@ public class AllApps3D extends RSSurfaceView
mLauncher.closeAllApps(true);
}
sRollo.setHomeSelected(SELECTED_NONE);
- sRollo.mState.save();
}
mCurrentIconIndex = -1;
} else if (mTouchTracking == TRACKING_FLING) {
- sRollo.mState.newTouchDown = 0;
- sRollo.mState.newPositionX = ev.getRawY() / getHeight();
-
mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, mMaxFlingVelocity);
- sRollo.mState.flingVelocity = mVelocityTracker.getYVelocity() / getHeight();
sRollo.clearSelectedIcon();
- sRollo.mState.save();
- sRollo.fling();
+ sRollo.fling(ev.getRawY() / getHeight(),
+ mVelocityTracker.getYVelocity() / getHeight());
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
@@ -766,7 +710,7 @@ public class AllApps3D extends RSSurfaceView
int pos = -1;
switch (mLastSelection) {
case SELECTION_ICONS:
- index = sRollo.mState.selectedIconIndex;
+ index = sRollo.mScript.get_gSelectedIconIndex();
if (index >= 0) {
ApplicationInfo info = mAllAppsList.get(index);
if (info.title != null) {
@@ -825,8 +769,8 @@ public class AllApps3D extends RSSurfaceView
return sRollo != null && mZoom > 0.001f;
}
- public boolean isOpaque() {
- return mZoom > 0.999f;
+ public boolean isAnimating() {
+ return isVisible() && mZoom <= 0.999f;
}
public void setApps(ArrayList<ApplicationInfo> list) {
@@ -858,13 +802,12 @@ public class AllApps3D extends RSSurfaceView
if (sRollo != null && reload) {
sRollo.setApps(list);
}
-
+
if (hasFocus() && mRestoreFocusIndex != -1) {
sRollo.selectIcon(mRestoreFocusIndex, SELECTED_FOCUSED);
- sRollo.mState.save();
mRestoreFocusIndex = -1;
}
-
+
mLocks &= ~LOCK_ICONS_PENDING;
}
@@ -881,7 +824,7 @@ public class AllApps3D extends RSSurfaceView
final int N = list.size();
if (sRollo != null) {
sRollo.pause();
- sRollo.reallocAppsList(sRollo.mState.iconCount + N);
+ sRollo.reallocAppsList(sRollo.mScript.get_gIconCount() + N);
}
for (int i=0; i<N; i++) {
@@ -951,17 +894,6 @@ public class AllApps3D extends RSSurfaceView
return -1;
}
- /*
- private static int countPages(int iconCount) {
- int iconsPerPage = getColumnsCount() * Defines.ROWS_PER_PAGE_PORTRAIT;
- int pages = iconCount / iconsPerPage;
- if (pages*iconsPerPage != iconCount) {
- pages++;
- }
- return pages;
- }
- */
-
class AAMessage extends RenderScript.RSMessage {
public void run() {
sRollo.mScrollPos = ((float)mData[0]) / (1 << 16);
@@ -993,23 +925,12 @@ public class AllApps3D extends RSSurfaceView
private int mHeight;
private Resources mRes;
- private Script mScript;
- private Script.Invokable mInvokeMove;
- private Script.Invokable mInvokeMoveTo;
- private Script.Invokable mInvokeFling;
- private Script.Invokable mInvokeResetWAR;
- private Script.Invokable mInvokeSetZoom;
-
- private ProgramStore mPSIcons;
- private ProgramFragment mPFTexMip;
- private ProgramFragment mPFTexMipAlpha;
- private ProgramFragment mPFTexNearest;
- private ProgramVertex mPV;
- private ProgramVertex mPVCurve;
- private SimpleMesh mMesh;
+ ScriptC_allapps mScript;
+
+ private Mesh mMesh;
private ProgramVertex.MatrixAllocation mPVA;
- private Allocation mUniformAlloc;
+ private ScriptField_VpConsts mUniformAlloc;
private Allocation mHomeButtonNormal;
private Allocation mHomeButtonFocused;
@@ -1022,28 +943,15 @@ public class AllApps3D extends RSSurfaceView
private Allocation[] mLabels;
private int[] mLabelIds;
private Allocation mAllocLabelIds;
- private Allocation mSelectedIcon;
private Bitmap mSelectionBitmap;
private Canvas mSelectionCanvas;
-
- private float mScrollPos;
- Params mParams;
- State mState;
+ private float mScrollPos;
AllApps3D mAllApps;
boolean mInitialize;
- class BaseAlloc {
- Allocation mAlloc;
- Type mType;
-
- void save() {
- mAlloc.data(this);
- }
- }
-
private boolean checkClickOK() {
return (Math.abs(mAllApps.mVelocity) < 0.4f) &&
(Math.abs(mScrollPos - Math.round(mScrollPos)) < 0.4f);
@@ -1061,41 +969,6 @@ public class AllApps3D extends RSSurfaceView
}
}
- class Params extends BaseAlloc {
- Params() {
- mType = Type.createFromClass(sRS, Params.class, 1, "ParamsClass");
- mAlloc = Allocation.createTyped(sRS, mType);
- save();
- }
- public int bubbleWidth;
- public int bubbleHeight;
- public int bubbleBitmapWidth;
- public int bubbleBitmapHeight;
-
- public int homeButtonWidth;
- public int homeButtonHeight;
- public int homeButtonTextureWidth;
- public int homeButtonTextureHeight;
- }
-
- class State extends BaseAlloc {
- public float newPositionX;
- public int newTouchDown;
- public float flingVelocity;
- public int iconCount;
- public int selectedIconIndex = -1;
- public int selectedIconTexture;
- public float zoomTarget;
- public int homeButtonId;
- public float targetPos;
-
- State() {
- mType = Type.createFromClass(sRS, State.class, 1, "StateClass");
- mAlloc = Allocation.createTyped(sRS, mType);
- save();
- }
- }
-
public RolloRS(AllApps3D allApps) {
mAllApps = allApps;
}
@@ -1104,16 +977,21 @@ public class AllApps3D extends RSSurfaceView
mRes = res;
mWidth = width;
mHeight = height;
+ mScript = new ScriptC_allapps(sRS, mRes, R.raw.allapps, true);
+
initProgramVertex();
initProgramFragment();
initProgramStore();
initGl();
initData();
- initRs();
+
+ mScript.bind_gIconIDs(mAllocIconIds);
+ mScript.bind_gLabelIDs(mAllocLabelIds);
+ sRS.contextBindRootScript(mScript);
}
public void initMesh() {
- SimpleMesh.TriangleMeshBuilder tm = new SimpleMesh.TriangleMeshBuilder(sRS, 2, 0);
+ Mesh.TriangleMeshBuilder tm = new Mesh.TriangleMeshBuilder(sRS, 2, 0);
for (int ct=0; ct < 16; ct++) {
float pos = (1.f / (16.f - 1)) * ct;
@@ -1124,12 +1002,55 @@ public class AllApps3D extends RSSurfaceView
tm.addTriangle(ct, ct+1, ct+2);
tm.addTriangle(ct+1, ct+3, ct+2);
}
- mMesh = tm.create();
- mMesh.setName("SMCell");
+ mMesh = tm.create(true);
+ mScript.set_gSMCell(mMesh);
+ }
+
+ Matrix4f getProjectionMatrix(int w, int h) {
+ // range -1,1 in the narrow axis at z = 0.
+ Matrix4f m1 = new Matrix4f();
+ Matrix4f m2 = new Matrix4f();
+
+ if(w > h) {
+ float aspect = ((float)w) / h;
+ m1.loadFrustum(-aspect,aspect, -1,1, 1,100);
+ } else {
+ float aspect = ((float)h) / w;
+ m1.loadFrustum(-1,1, -aspect,aspect, 1,100);
+ }
+
+ m2.loadRotate(180, 0, 1, 0);
+ m1.loadMultiply(m1, m2);
+
+ m2.loadScale(-2, 2, 1);
+ m1.loadMultiply(m1, m2);
+
+ m2.loadTranslate(0, 0, 2);
+ m1.loadMultiply(m1, m2);
+ return m1;
}
void resize(int w, int h) {
- mPVA.setupProjectionNormalized(w, h);
+ Matrix4f proj = getProjectionMatrix(w, h);
+ mPVA.loadProjection(proj);
+
+ if (mUniformAlloc != null) {
+ ScriptField_VpConsts.Item i = new ScriptField_VpConsts.Item();
+ i.Proj = proj;
+ i.ScaleOffset.x = (2.f / 480.f);
+ i.ScaleOffset.y = 0;
+ i.ScaleOffset.z = -((float)w / 2) - 0.25f;
+ i.ScaleOffset.w = -380.25f;
+ i.BendPos.x = 120.f;
+ i.BendPos.y = 680.f;
+ if (w > h) {
+ i.ScaleOffset.z = 40.f;
+ i.ScaleOffset.w = h - 40.f;
+ i.BendPos.y = 1.f;
+ }
+ mUniformAlloc.set(i, 0, true);
+ }
+
mWidth = w;
mHeight = h;
}
@@ -1140,22 +1061,18 @@ public class AllApps3D extends RSSurfaceView
ProgramVertex.Builder pvb = new ProgramVertex.Builder(sRS, null, null);
pvb.setTextureMatrixEnable(true);
- mPV = pvb.create();
- mPV.setName("PV");
- mPV.bindAllocation(mPVA);
+ ProgramVertex pv = pvb.create();
+ pv.bindAllocation(mPVA);
+ sRS.contextBindProgramVertex(pv);
- Element.Builder eb = new Element.Builder(sRS);
- eb.add(Element.createVector(sRS, Element.DataType.FLOAT_32, 2), "ImgSize");
- eb.add(Element.createVector(sRS, Element.DataType.FLOAT_32, 4), "Position");
- eb.add(Element.createVector(sRS, Element.DataType.FLOAT_32, 2), "BendPos");
- eb.add(Element.createVector(sRS, Element.DataType.FLOAT_32, 4), "ScaleOffset");
- Element e = eb.create();
-
- mUniformAlloc = Allocation.createSized(sRS, e, 1);
+ mUniformAlloc = new ScriptField_VpConsts(sRS, 1);
+ mScript.bind_vpConstants(mUniformAlloc);
initMesh();
ProgramVertex.ShaderBuilder sb = new ProgramVertex.ShaderBuilder(sRS);
- String t = "void main() {\n" +
+ String t = "varying vec4 varColor;\n" +
+ "varying vec2 varTex0;\n" +
+ "void main() {\n" +
// Animation
" float ani = UNI_Position.z;\n" +
@@ -1205,21 +1122,18 @@ public class AllApps3D extends RSSurfaceView
" pos.z -= ani * 1.5;\n" +
" lum *= 1.0 - ani;\n" +
- " gl_Position = UNI_MVP * pos;\n" +
+ " gl_Position = UNI_Proj * pos;\n" +
" varColor.rgba = vec4(lum, lum, lum, 1.0);\n" +
" varTex0.xy = ATTRIB_position;\n" +
" varTex0.y = 1.0 - varTex0.y;\n" +
- " varTex0.zw = vec2(0.0, 0.0);\n" +
"}\n";
sb.setShader(t);
sb.addConstant(mUniformAlloc.getType());
- sb.addInput(mMesh.getVertexType(0).getElement());
- mPVCurve = sb.create();
- mPVCurve.setName("PVCurve");
- mPVCurve.bindAllocation(mPVA);
- mPVCurve.bindConstants(mUniformAlloc, 1);
+ sb.addInput(mMesh.getVertexAllocation(0).getType().getElement());
+ ProgramVertex pvc = sb.create();
+ pvc.bindConstants(mUniformAlloc.getAllocation(), 0);
- sRS.contextBindProgramVertex(mPV);
+ mScript.set_gPVCurve(pvc);
}
private void initProgramFragment() {
@@ -1237,20 +1151,23 @@ public class AllApps3D extends RSSurfaceView
ProgramFragment.Builder bf = new ProgramFragment.Builder(sRS);
bf.setTexture(ProgramFragment.Builder.EnvMode.MODULATE,
ProgramFragment.Builder.Format.RGBA, 0);
- mPFTexMip = bf.create();
- mPFTexMip.setName("PFTexMip");
- mPFTexMip.bindSampler(linear, 0);
+ bf.setVaryingColor(true);
+ ProgramFragment pfTexMip = bf.create();
+ pfTexMip.bindSampler(linear, 0);
- mPFTexNearest = bf.create();
- mPFTexNearest.setName("PFTexNearest");
- mPFTexNearest.bindSampler(nearest, 0);
+ bf.setVaryingColor(false);
+ ProgramFragment pfTexNearest = bf.create();
+ pfTexNearest.bindSampler(nearest, 0);
bf.setTexture(ProgramFragment.Builder.EnvMode.MODULATE,
ProgramFragment.Builder.Format.ALPHA, 0);
- mPFTexMipAlpha = bf.create();
- mPFTexMipAlpha.setName("PFTexMipAlpha");
- mPFTexMipAlpha.bindSampler(linear, 0);
+ bf.setVaryingColor(true);
+ ProgramFragment pfTexMipAlpha = bf.create();
+ pfTexMipAlpha.bindSampler(linear, 0);
+ mScript.set_gPFTexNearest(pfTexNearest);
+ mScript.set_gPFTexMip(pfTexMip);
+ mScript.set_gPFTexMipAlpha(pfTexMipAlpha);
}
private void initProgramStore() {
@@ -1260,23 +1177,17 @@ public class AllApps3D extends RSSurfaceView
bs.setDitherEnable(true);
bs.setBlendFunc(ProgramStore.BlendSrcFunc.SRC_ALPHA,
ProgramStore.BlendDstFunc.ONE_MINUS_SRC_ALPHA);
- mPSIcons = bs.create();
- mPSIcons.setName("PSIcons");
+ mScript.set_gPS(bs.create());
}
private void initGl() {
}
private void initData() {
- mParams = new Params();
- mState = new State();
-
- final Utilities.BubbleText bubble = new Utilities.BubbleText(mAllApps.getContext());
-
- mParams.bubbleWidth = bubble.getBubbleWidth();
- mParams.bubbleHeight = bubble.getMaxBubbleHeight();
- mParams.bubbleBitmapWidth = bubble.getBitmapWidth();
- mParams.bubbleBitmapHeight = bubble.getBitmapHeight();
+ mScript.set_COLUMNS_PER_PAGE_PORTRAIT(Defines.COLUMNS_PER_PAGE_PORTRAIT);
+ mScript.set_ROWS_PER_PAGE_PORTRAIT(Defines.ROWS_PER_PAGE_PORTRAIT);
+ mScript.set_COLUMNS_PER_PAGE_LANDSCAPE(Defines.COLUMNS_PER_PAGE_LANDSCAPE);
+ mScript.set_ROWS_PER_PAGE_LANDSCAPE(Defines.ROWS_PER_PAGE_LANDSCAPE);
mHomeButtonNormal = Allocation.createFromBitmapResource(sRS, mRes,
R.drawable.home_button_normal, Element.RGBA_8888(sRS), false);
@@ -1287,15 +1198,8 @@ public class AllApps3D extends RSSurfaceView
mHomeButtonPressed = Allocation.createFromBitmapResource(sRS, mRes,
R.drawable.home_button_pressed, Element.RGBA_8888(sRS), false);
mHomeButtonPressed.uploadToTexture(0);
- mParams.homeButtonWidth = 76;
- mParams.homeButtonHeight = 68;
- mParams.homeButtonTextureWidth = 128;
- mParams.homeButtonTextureHeight = 128;
- mState.homeButtonId = mHomeButtonNormal.getID();
-
- mParams.save();
- mState.save();
+ mScript.set_gHomeButton(mHomeButtonNormal);
mSelectionBitmap = Bitmap.createBitmap(Defines.SELECTION_TEXTURE_WIDTH_PX,
Defines.SELECTION_TEXTURE_HEIGHT_PX, Bitmap.Config.ARGB_8888);
@@ -1304,30 +1208,6 @@ public class AllApps3D extends RSSurfaceView
setApps(null);
}
- private void initRs() {
- ScriptC.Builder sb = new ScriptC.Builder(sRS);
- sb.setScript(mRes, R.raw.allapps);
- sb.setRoot(true);
- sb.addDefines(mAllApps.mDefines);
- sb.setType(mParams.mType, "params", Defines.ALLOC_PARAMS);
- sb.setType(mState.mType, "state", Defines.ALLOC_STATE);
- sb.setType(mUniformAlloc.getType(), "vpConstants", Defines.ALLOC_VP_CONSTANTS);
- mInvokeMove = sb.addInvokable("move");
- mInvokeFling = sb.addInvokable("fling");
- mInvokeMoveTo = sb.addInvokable("moveTo");
- mInvokeResetWAR = sb.addInvokable("resetHWWar");
- mInvokeSetZoom = sb.addInvokable("setZoom");
- mScript = sb.create();
- mScript.setClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- mScript.bindAllocation(mParams.mAlloc, Defines.ALLOC_PARAMS);
- mScript.bindAllocation(mState.mAlloc, Defines.ALLOC_STATE);
- mScript.bindAllocation(mAllocIconIds, Defines.ALLOC_ICON_IDS);
- mScript.bindAllocation(mAllocLabelIds, Defines.ALLOC_LABEL_IDS);
- mScript.bindAllocation(mUniformAlloc, Defines.ALLOC_VP_CONSTANTS);
-
- sRS.contextBindRootScript(mScript);
- }
-
void dirtyCheck() {
if (sZoomDirty) {
setZoom(mAllApps.sNextZoom, mAllApps.sAnimateNextZoom);
@@ -1345,20 +1225,21 @@ public class AllApps3D extends RSSurfaceView
mIcons = new Allocation[count];
mIconIds = new int[allocCount];
- mAllocIconIds = Allocation.createSized(sRS, Element.USER_I32(sRS), allocCount);
+ mAllocIconIds = Allocation.createSized(sRS, Element.I32(sRS), allocCount);
mLabels = new Allocation[count];
mLabelIds = new int[allocCount];
- mAllocLabelIds = Allocation.createSized(sRS, Element.USER_I32(sRS), allocCount);
+ mAllocLabelIds = Allocation.createSized(sRS, Element.I32(sRS), allocCount);
- mState.iconCount = count;
- for (int i=0; i < mState.iconCount; i++) {
+ mScript.set_gIconCount(count);
+ for (int i=0; i < count; i++) {
createAppIconAllocations(i, list.get(i));
}
- for (int i=0; i < mState.iconCount; i++) {
+ for (int i=0; i < count; i++) {
uploadAppIcon(i, list.get(i));
}
saveAppsList();
+ android.util.Log.e("rs", "setApps");
sRollo.resume();
}
@@ -1367,15 +1248,7 @@ public class AllApps3D extends RSSurfaceView
sRollo.clearSelectedIcon();
sRollo.setHomeSelected(SELECTED_NONE);
}
- if (zoom > 0.001f) {
- sRollo.mState.zoomTarget = zoom;
- } else {
- sRollo.mState.zoomTarget = 0;
- }
- sRollo.mState.save();
- if (!animate) {
- sRollo.mInvokeSetZoom.execute();
- }
+ sRollo.mScript.invoke_setZoom(zoom, animate ? 1 : 0);
}
private void createAppIconAllocations(int index, ApplicationInfo item) {
@@ -1405,13 +1278,13 @@ public class AllApps3D extends RSSurfaceView
private void reallocAppsList(int count) {
Allocation[] icons = new Allocation[count];
int[] iconIds = new int[count];
- mAllocIconIds = Allocation.createSized(sRS, Element.USER_I32(sRS), count);
+ mAllocIconIds = Allocation.createSized(sRS, Element.I32(sRS), count);
Allocation[] labels = new Allocation[count];
int[] labelIds = new int[count];
- mAllocLabelIds = Allocation.createSized(sRS, Element.USER_I32(sRS), count);
+ mAllocLabelIds = Allocation.createSized(sRS, Element.I32(sRS), count);
- final int oldCount = sRollo.mState.iconCount;
+ final int oldCount = sRollo.mScript.get_gIconCount();
System.arraycopy(mIcons, 0, icons, 0, oldCount);
System.arraycopy(mIconIds, 0, iconIds, 0, oldCount);
@@ -1428,7 +1301,7 @@ public class AllApps3D extends RSSurfaceView
* Handle the allocations for the new app. Make sure you call saveAppsList when done.
*/
private void addApp(int index, ApplicationInfo item) {
- final int count = mState.iconCount - index;
+ final int count = mScript.get_gIconCount() - index;
final int dest = index + 1;
System.arraycopy(mIcons, index, mIcons, dest, count);
@@ -1438,14 +1311,15 @@ public class AllApps3D extends RSSurfaceView
createAppIconAllocations(index, item);
uploadAppIcon(index, item);
- sRollo.mState.iconCount++;
+
+ mScript.set_gIconCount(mScript.get_gIconCount() + 1);
}
/**
* Handle the allocations for the removed app. Make sure you call saveAppsList when done.
*/
private void removeApp(int index) {
- final int count = mState.iconCount - index - 1;
+ final int count = mScript.get_gIconCount() - index - 1;
final int src = index + 1;
System.arraycopy(mIcons, src, mIcons, index, count);
@@ -1453,8 +1327,8 @@ public class AllApps3D extends RSSurfaceView
System.arraycopy(mLabels, src, mLabels, index, count);
System.arraycopy(mLabelIds, src, mLabelIds, index, count);
- sRollo.mState.iconCount--;
- final int last = mState.iconCount;
+ mScript.set_gIconCount(mScript.get_gIconCount() - 1);
+ final int last = mScript.get_gIconCount();
mIcons[last] = null;
mIconIds[last] = 0;
@@ -1471,31 +1345,21 @@ public class AllApps3D extends RSSurfaceView
mAllocIconIds.data(mIconIds);
mAllocLabelIds.data(mLabelIds);
- mScript.bindAllocation(mAllocIconIds, Defines.ALLOC_ICON_IDS);
- mScript.bindAllocation(mAllocLabelIds, Defines.ALLOC_LABEL_IDS);
-
- mState.save();
-
- // Note: mScript may be null if we haven't initialized it yet.
- // In that case, this is a no-op.
- if (mInvokeResetWAR != null) {
- mInvokeResetWAR.execute();
- }
+ mScript.bind_gIconIDs(mAllocIconIds);
+ mScript.bind_gLabelIDs(mAllocLabelIds);
}
}
- void fling() {
- mInvokeFling.execute();
+ void fling(float pos, float v) {
+ mScript.invoke_fling(pos, v);
}
- void move() {
- mInvokeMove.execute();
+ void move(float pos) {
+ mScript.invoke_move(pos);
}
void moveTo(float row) {
- mState.targetPos = row;
- mState.save();
- mInvokeMoveTo.execute();
+ mScript.invoke_moveTo(row);
}
/**
@@ -1525,7 +1389,7 @@ public class AllApps3D extends RSSurfaceView
if (mAllApps != null) {
mAllApps.mRestoreFocusIndex = index;
}
- mState.selectedIconIndex = -1;
+ mScript.set_gSelectedIconIndex(-1);
if (mAllApps.mLastSelection == SELECTION_ICONS) {
mAllApps.mLastSelection = SELECTION_NONE;
}
@@ -1534,8 +1398,8 @@ public class AllApps3D extends RSSurfaceView
mAllApps.mLastSelection = SELECTION_ICONS;
}
- int prev = mState.selectedIconIndex;
- mState.selectedIconIndex = index;
+ int prev = mScript.get_gSelectedIconIndex();
+ mScript.set_gSelectedIconIndex(index);
ApplicationInfo info = appsList.get(index);
Bitmap selectionBitmap = mSelectionBitmap;
@@ -1544,10 +1408,10 @@ public class AllApps3D extends RSSurfaceView
selectionBitmap.getWidth(), selectionBitmap.getHeight(),
pressed == SELECTED_PRESSED, info.iconBitmap);
- mSelectedIcon = Allocation.createFromBitmap(sRS, selectionBitmap,
+ Allocation si = Allocation.createFromBitmap(sRS, selectionBitmap,
Element.RGBA_8888(sRS), false);
- mSelectedIcon.uploadToTexture(0);
- mState.selectedIconTexture = mSelectedIcon.getID();
+ si.uploadToTexture(0);
+ mScript.set_gSelectedIconTexture(si);
if (prev != index) {
if (info.title != null && info.title.length() > 0) {
@@ -1562,24 +1426,24 @@ public class AllApps3D extends RSSurfaceView
* You need to call save() on mState on your own after calling this.
*/
void clearSelectedIcon() {
- mState.selectedIconIndex = -1;
+ mScript.set_gSelectedIconIndex(-1);
}
void setHomeSelected(int mode) {
final int prev = mAllApps.mLastSelection;
switch (mode) {
case SELECTED_NONE:
- mState.homeButtonId = mHomeButtonNormal.getID();
+ mScript.set_gHomeButton(mHomeButtonNormal);
break;
case SELECTED_FOCUSED:
mAllApps.mLastSelection = SELECTION_HOME;
- mState.homeButtonId = mHomeButtonFocused.getID();
+ mScript.set_gHomeButton(mHomeButtonFocused);
if (prev != SELECTION_HOME) {
mAllApps.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
break;
case SELECTED_PRESSED:
- mState.homeButtonId = mHomeButtonPressed.getID();
+ mScript.set_gHomeButton(mHomeButtonPressed);
break;
}
}
@@ -1599,23 +1463,15 @@ public class AllApps3D extends RSSurfaceView
Log.d(TAG, "sRollo.mLabelIds.length=" + mLabelIds.length);
}
Log.d(TAG, "sRollo.mLabelIds=" + Arrays.toString(mLabelIds));
- Log.d(TAG, "sRollo.mState.newPositionX=" + mState.newPositionX);
- Log.d(TAG, "sRollo.mState.newTouchDown=" + mState.newTouchDown);
- Log.d(TAG, "sRollo.mState.flingVelocity=" + mState.flingVelocity);
- Log.d(TAG, "sRollo.mState.iconCount=" + mState.iconCount);
- Log.d(TAG, "sRollo.mState.selectedIconIndex=" + mState.selectedIconIndex);
- Log.d(TAG, "sRollo.mState.selectedIconTexture=" + mState.selectedIconTexture);
- Log.d(TAG, "sRollo.mState.zoomTarget=" + mState.zoomTarget);
- Log.d(TAG, "sRollo.mState.homeButtonId=" + mState.homeButtonId);
- Log.d(TAG, "sRollo.mState.targetPos=" + mState.targetPos);
- Log.d(TAG, "sRollo.mParams.bubbleWidth=" + mParams.bubbleWidth);
- Log.d(TAG, "sRollo.mParams.bubbleHeight=" + mParams.bubbleHeight);
- Log.d(TAG, "sRollo.mParams.bubbleBitmapWidth=" + mParams.bubbleBitmapWidth);
- Log.d(TAG, "sRollo.mParams.bubbleBitmapHeight=" + mParams.bubbleBitmapHeight);
- Log.d(TAG, "sRollo.mParams.homeButtonWidth=" + mParams.homeButtonWidth);
- Log.d(TAG, "sRollo.mParams.homeButtonHeight=" + mParams.homeButtonHeight);
- Log.d(TAG, "sRollo.mParams.homeButtonTextureWidth=" + mParams.homeButtonTextureWidth);
- Log.d(TAG, "sRollo.mParams.homeButtonTextureHeight=" + mParams.homeButtonTextureHeight);
+ //Log.d(TAG, "sRollo.mState.newPositionX=" + mState.newPositionX);
+ //Log.d(TAG, "sRollo.mState.newTouchDown=" + mState.newTouchDown);
+ //Log.d(TAG, "sRollo.mState.flingVelocity=" + mState.flingVelocity);
+ //Log.d(TAG, "sRollo.mState.iconCount=" + mState.iconCount);
+ //Log.d(TAG, "sRollo.mState.selectedIconIndex=" + mState.selectedIconIndex);
+ //Log.d(TAG, "sRollo.mState.selectedIconTexture=" + mState.selectedIconTexture);
+ //Log.d(TAG, "sRollo.mState.zoomTarget=" + mState.zoomTarget);
+ //Log.d(TAG, "sRollo.mState.homeButtonId=" + mState.homeButtonId);
+ //Log.d(TAG, "sRollo.mState.targetPos=" + mState.targetPos);
}
}
@@ -1646,5 +1502,3 @@ public class AllApps3D extends RSSurfaceView
}
}
}
-
-
diff --git a/src/com/android/launcher2/AllAppsList.java b/src/com/android/launcher2/AllAppsList.java
index 3a5baeaa3..4c9bc5eca 100644
--- a/src/com/android/launcher2/AllAppsList.java
+++ b/src/com/android/launcher2/AllAppsList.java
@@ -16,17 +16,16 @@
package com.android.launcher2;
+import java.util.ArrayList;
+import java.util.List;
+
import android.content.ComponentName;
-import android.content.Intent;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
/**
* Stores the list of all applications for the all apps view.
@@ -92,7 +91,7 @@ class AllAppsList {
if (matches.size() > 0) {
for (ResolveInfo info : matches) {
- add(new ApplicationInfo(info, mIconCache));
+ add(new ApplicationInfo(context.getPackageManager(), info, mIconCache));
}
}
}
@@ -143,7 +142,7 @@ class AllAppsList {
info.activityInfo.applicationInfo.packageName,
info.activityInfo.name);
if (applicationInfo == null) {
- add(new ApplicationInfo(info, mIconCache));
+ add(new ApplicationInfo(context.getPackageManager(), info, mIconCache));
} else {
mIconCache.remove(applicationInfo.componentName);
mIconCache.getTitleAndIcon(applicationInfo, info);
diff --git a/src/com/android/launcher2/AllAppsPagedView.java b/src/com/android/launcher2/AllAppsPagedView.java
new file mode 100644
index 000000000..351384f93
--- /dev/null
+++ b/src/com/android/launcher2/AllAppsPagedView.java
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.ActionMode;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.widget.Checkable;
+import android.widget.TextView;
+
+import com.android.launcher.R;
+
+/**
+ * An implementation of PagedView that populates the pages of the workspace
+ * with all of the user's applications.
+ */
+public class AllAppsPagedView extends PagedView
+ implements AllAppsView, View.OnClickListener, View.OnLongClickListener, DragSource,
+ DropTarget, ActionMode.Callback {
+
+ private static final String TAG = "AllAppsPagedView";
+ private static final boolean DEBUG = false;
+
+ private static final int MENU_DELETE_APP = 1;
+ private static final int MENU_APP_INFO = 2;
+
+ private Launcher mLauncher;
+ private DragController mDragController;
+
+ // preserve compatibility with 3D all apps:
+ // 0.0 -> hidden
+ // 1.0 -> shown and opaque
+ // intermediate values -> partially shown & partially opaque
+ private float mZoom;
+
+ // set of all applications
+ private ArrayList<ApplicationInfo> mApps;
+ private ArrayList<ApplicationInfo> mFilteredApps;
+
+ // the types of applications to filter
+ static final int ALL_APPS_FLAG = -1;
+ private int mAppFilter = ALL_APPS_FLAG;
+
+ private final LayoutInflater mInflater;
+
+ private ViewGroup mOrigInfoButtonParent;
+ private LayoutParams mOrigInfoButtonLayoutParams;
+
+ private ViewGroup mOrigDeleteZoneParent;
+ private LayoutParams mOrigDeleteZoneLayoutParams;
+
+ public AllAppsPagedView(Context context) {
+ this(context, null);
+ }
+
+ public AllAppsPagedView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AllAppsPagedView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedView, defStyle, 0);
+ mCellCountX = a.getInt(R.styleable.PagedView_cellCountX, 6);
+ mCellCountY = a.getInt(R.styleable.PagedView_cellCountY, 4);
+ mInflater = LayoutInflater.from(context);
+ a.recycle();
+ setSoundEffectsEnabled(false);
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+ mCenterPagesVertically = false;
+ }
+
+ @Override
+ public void setLauncher(Launcher launcher) {
+ mLauncher = launcher;
+ mLauncher.setAllAppsPagedView(this);
+ }
+
+ @Override
+ public void setDragController(DragController dragger) {
+ mDragController = dragger;
+ }
+
+ public void setAppFilter(int filterType) {
+ mAppFilter = filterType;
+ if (mApps != null) {
+ mFilteredApps = rebuildFilteredApps(mApps);
+ setCurrentPage(0);
+ invalidatePageData();
+ }
+ }
+
+ @Override
+ public void zoom(float zoom, boolean animate) {
+ mZoom = zoom;
+ cancelLongPress();
+
+ if (isVisible()) {
+ getParent().bringChildToFront(this);
+ setVisibility(View.VISIBLE);
+ if (animate) {
+ startAnimation(AnimationUtils.loadAnimation(getContext(),
+ R.anim.all_apps_2d_fade_in));
+ } else {
+ onAnimationEnd();
+ }
+ } else {
+ if (animate) {
+ startAnimation(AnimationUtils.loadAnimation(getContext(),
+ R.anim.all_apps_2d_fade_out));
+ } else {
+ onAnimationEnd();
+ }
+ }
+ }
+
+ protected void onAnimationEnd() {
+ if (!isVisible()) {
+ setVisibility(View.GONE);
+ mZoom = 0.0f;
+
+ endChoiceMode();
+ } else {
+ mZoom = 1.0f;
+ }
+
+ if (mLauncher != null)
+ mLauncher.zoomed(mZoom);
+ }
+
+ private int getChildIndexForGrandChild(View v) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; ++i) {
+ final PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i);
+ if (layout.indexOfChild(v) > -1) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public void onClick(View v) {
+ // if we are already in a choice mode, then just change the selection
+ if (v instanceof Checkable) {
+ if (!isChoiceMode(CHOICE_MODE_NONE)) {
+ Checkable c = (Checkable) v;
+ if (isChoiceMode(CHOICE_MODE_SINGLE)) {
+ // Uncheck all the other grandchildren, and toggle the clicked one
+ boolean wasChecked = c.isChecked();
+ resetCheckedGrandchildren();
+ c.setChecked(!wasChecked);
+ } else {
+ c.toggle();
+ }
+ if (getCheckedGrandchildren().size() == 0) {
+ endChoiceMode();
+ }
+
+ return;
+ }
+ }
+
+ // otherwise continue and launch the application
+ int childIndex = getChildIndexForGrandChild(v);
+ if (childIndex == getCurrentPage()) {
+ final ApplicationInfo app = (ApplicationInfo) v.getTag();
+
+ // animate some feedback to the click
+ animateClickFeedback(v, new Runnable() {
+ @Override
+ public void run() {
+ mLauncher.startActivitySafely(app.intent, app);
+ }
+ });
+
+ endChoiceMode();
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ if (!v.isInTouchMode()) {
+ return false;
+ }
+
+ if (v instanceof Checkable) {
+ // In preparation for drag, we always reset the checked grand children regardless of
+ // what choice mode we are in
+ resetCheckedGrandchildren();
+
+ // Toggle the selection on the dragged app
+ Checkable c = (Checkable) v;
+ c.toggle();
+ }
+
+ // Start choice mode AFTER the item is selected
+ if (isChoiceMode(CHOICE_MODE_NONE)) {
+ startChoiceMode(CHOICE_MODE_SINGLE, this);
+ }
+
+ ApplicationInfo app = (ApplicationInfo) v.getTag();
+ app = new ApplicationInfo(app);
+
+ mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1);
+ mDragController.startDrag(v, this, app, DragController.DRAG_ACTION_COPY);
+ return true;
+ }
+
+ @Override
+ public void onDropCompleted(View target, boolean success) {
+ // close the choice action mode if we have a proper drop
+ if (target != this) {
+ endChoiceMode();
+ }
+ mLauncher.getWorkspace().onDragStopped();
+ }
+
+ @Override
+ public boolean isVisible() {
+ return mZoom > 0.001f;
+ }
+
+ @Override
+ public boolean isAnimating() {
+ return (getAnimation() != null);
+ }
+
+ private ArrayList<ApplicationInfo> rebuildFilteredApps(ArrayList<ApplicationInfo> apps) {
+ ArrayList<ApplicationInfo> filteredApps = new ArrayList<ApplicationInfo>();
+ if (mAppFilter == ALL_APPS_FLAG) {
+ return apps;
+ } else {
+ final int length = apps.size();
+ for (int i = 0; i < length; ++i) {
+ ApplicationInfo info = apps.get(i);
+ if ((info.flags & mAppFilter) > 0) {
+ filteredApps.add(info);
+ }
+ }
+ }
+ return filteredApps;
+ }
+
+ @Override
+ public void setApps(ArrayList<ApplicationInfo> list) {
+ mApps = list;
+ Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
+ mFilteredApps = rebuildFilteredApps(mApps);
+ mPageViewIconCache.clear();
+ invalidatePageData();
+ }
+
+ private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
+ // we add it in place, in alphabetical order
+ final int count = list.size();
+ for (int i = 0; i < count; ++i) {
+ final ApplicationInfo info = list.get(i);
+ final int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR);
+ if (index < 0) {
+ mApps.add(-(index + 1), info);
+ }
+ }
+ mFilteredApps = rebuildFilteredApps(mApps);
+ }
+ @Override
+ public void addApps(ArrayList<ApplicationInfo> list) {
+ addAppsWithoutInvalidate(list);
+ invalidatePageData();
+ }
+
+ private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
+ // End the choice mode if any of the items in the list that are being removed are
+ // currently selected
+ ArrayList<Checkable> checkedList = getCheckedGrandchildren();
+ HashSet<ApplicationInfo> checkedAppInfos = new HashSet<ApplicationInfo>();
+ for (Checkable checked : checkedList) {
+ PagedViewIcon icon = (PagedViewIcon) checked;
+ checkedAppInfos.add((ApplicationInfo) icon.getTag());
+ }
+ for (ApplicationInfo info : list) {
+ if (checkedAppInfos.contains(info)) {
+ endChoiceMode();
+ break;
+ }
+ }
+
+ // Loop through all the apps and remove apps that have the same component
+ final int length = list.size();
+ for (int i = 0; i < length; ++i) {
+ final ApplicationInfo info = list.get(i);
+ int removeIndex = findAppByComponent(mApps, info);
+ if (removeIndex > -1) {
+ mApps.remove(removeIndex);
+ mPageViewIconCache.removeOutline(info);
+ }
+ }
+ mFilteredApps = rebuildFilteredApps(mApps);
+ }
+ @Override
+ public void removeApps(ArrayList<ApplicationInfo> list) {
+ removeAppsWithoutInvalidate(list);
+ invalidatePageData();
+ }
+
+ @Override
+ public void updateApps(ArrayList<ApplicationInfo> list) {
+ removeAppsWithoutInvalidate(list);
+ addAppsWithoutInvalidate(list);
+ invalidatePageData();
+ }
+
+ private int findAppByComponent(ArrayList<ApplicationInfo> list, ApplicationInfo item) {
+ ComponentName removeComponent = item.intent.getComponent();
+ final int length = list.size();
+ for (int i = 0; i < length; ++i) {
+ ApplicationInfo info = list.get(i);
+ if (info.intent.getComponent().equals(removeComponent)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public void dumpState() {
+ ApplicationInfo.dumpApplicationInfoList(TAG, "mApps", mApps);
+ }
+
+ @Override
+ public void surrender() {
+ // do nothing?
+ }
+
+ @Override
+ public void syncPages() {
+ // ensure that we have the right number of pages (min of 1, since we have placeholders)
+ int numPages = Math.max(1,
+ (int) Math.ceil((float) mFilteredApps.size() / (mCellCountX * mCellCountY)));
+ int curNumPages = getChildCount();
+ // remove any extra pages after the "last" page
+ int extraPageDiff = curNumPages - numPages;
+ for (int i = 0; i < extraPageDiff; ++i) {
+ removeViewAt(numPages);
+ }
+ // add any necessary pages
+ for (int i = curNumPages; i < numPages; ++i) {
+ PagedViewCellLayout layout = new PagedViewCellLayout(getContext());
+ layout.setCellCount(mCellCountX, mCellCountY);
+ layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
+ mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
+ layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
+ addView(layout);
+ }
+
+ // bound the current page
+ setCurrentPage(Math.max(0, Math.min(numPages - 1, getCurrentPage())));
+ }
+
+ @Override
+ public void syncPageItems(int page) {
+ // Ensure that we have the right number of items on the pages
+ final int cellsPerPage = mCellCountX * mCellCountY;
+ final int startIndex = page * cellsPerPage;
+ final int endIndex = Math.min(startIndex + cellsPerPage, mFilteredApps.size());
+ PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page);
+
+ if (!mFilteredApps.isEmpty()) {
+ int curNumPageItems = layout.getChildCount();
+ int numPageItems = endIndex - startIndex;
+
+ // If we were previously an empty page, then restart anew
+ boolean wasEmptyPage = false;
+ if (curNumPageItems == 1) {
+ View icon = layout.getChildAt(0);
+ if (icon.getTag() == null) {
+ wasEmptyPage = true;
+ }
+ }
+
+ if (wasEmptyPage) {
+ // Remove all the previous items
+ curNumPageItems = 0;
+ layout.removeAllViews();
+ } else {
+ // Remove any extra items
+ int extraPageItemsDiff = curNumPageItems - numPageItems;
+ for (int i = 0; i < extraPageItemsDiff; ++i) {
+ layout.removeViewAt(numPageItems);
+ }
+ }
+
+ // Add any necessary items
+ for (int i = curNumPageItems; i < numPageItems; ++i) {
+ TextView text = (TextView) mInflater.inflate(
+ R.layout.all_apps_paged_view_application, layout, false);
+ text.setOnClickListener(this);
+ text.setOnLongClickListener(this);
+
+ layout.addViewToCellLayout(text, -1, i,
+ new PagedViewCellLayout.LayoutParams(0, 0, 1, 1));
+ }
+
+ // Actually reapply to the existing text views
+ for (int i = startIndex; i < endIndex; ++i) {
+ final int index = i - startIndex;
+ final ApplicationInfo info = mFilteredApps.get(i);
+ PagedViewIcon icon = (PagedViewIcon) layout.getChildAt(index);
+ icon.applyFromApplicationInfo(info, mPageViewIconCache, true);
+
+ PagedViewCellLayout.LayoutParams params =
+ (PagedViewCellLayout.LayoutParams) icon.getLayoutParams();
+ params.cellX = index % mCellCountX;
+ params.cellY = index / mCellCountX;
+ }
+
+ // Default to left-aligned icons
+ layout.enableCenteredContent(false);
+ } else {
+ // There are no items, so show the user a small message
+ TextView icon = (TextView) mInflater.inflate(
+ R.layout.all_apps_no_items_placeholder, layout, false);
+ switch (mAppFilter) {
+ case ApplicationInfo.DOWNLOADED_FLAG:
+ icon.setText(mContext.getString(R.string.all_apps_no_downloads));
+ break;
+ default: break;
+ }
+
+ // Center-align the message
+ layout.enableCenteredContent(true);
+ layout.removeAllViews();
+ layout.addViewToCellLayout(icon, -1, 0,
+ new PagedViewCellLayout.LayoutParams(0, 0, 2, 1));
+ }
+ }
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ mode.setTitle(R.string.cab_app_selection_text);
+
+ // Until the workspace has a selection mode and the CAB supports drag-and-drop, we
+ // take a hybrid approach: grab the views from the workspace and stuff them into the CAB.
+ // When the action mode is done, restore the views to their original place in the toolbar.
+
+ ApplicationInfoDropTarget infoButton =
+ (ApplicationInfoDropTarget) mLauncher.findViewById(R.id.info_button);
+ mOrigInfoButtonParent = (ViewGroup) infoButton.getParent();
+ mOrigInfoButtonLayoutParams = infoButton.getLayoutParams();
+ mOrigInfoButtonParent.removeView(infoButton);
+ infoButton.setManageVisibility(false);
+ infoButton.setVisibility(View.VISIBLE);
+ infoButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ final ApplicationInfo appInfo = (ApplicationInfo) getChosenItem();
+ mLauncher.startApplicationDetailsActivity(appInfo.componentName);
+ }
+ });
+
+ DeleteZone deleteZone = (DeleteZone) mLauncher.findViewById(R.id.delete_zone);
+ mOrigDeleteZoneParent = (ViewGroup) deleteZone.getParent();
+ mOrigDeleteZoneLayoutParams = deleteZone.getLayoutParams();
+ mOrigDeleteZoneParent.removeView(deleteZone);
+ deleteZone.setManageVisibility(false);
+ deleteZone.setVisibility(View.VISIBLE);
+ deleteZone.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ final ApplicationInfo appInfo = (ApplicationInfo) getChosenItem();
+ mLauncher.startApplicationUninstallActivity(appInfo);
+ }
+ });
+
+ menu.add(0, MENU_APP_INFO, 0, R.string.cab_menu_app_info).setActionView(infoButton);
+ menu.add(0, MENU_DELETE_APP, 0, R.string.cab_menu_delete_app).setActionView(deleteZone);
+
+ return true;
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ mDragController.addDropTarget(this);
+ return true;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ final Menu menu = mode.getMenu();
+
+ // Re-parent the drop targets into the toolbar, and restore their layout params
+
+ ApplicationInfoDropTarget infoButton =
+ (ApplicationInfoDropTarget) menu.findItem(MENU_APP_INFO).getActionView();
+ ((ViewGroup) infoButton.getParent()).removeView(infoButton);
+ mOrigInfoButtonParent.addView(infoButton, mOrigInfoButtonLayoutParams);
+ infoButton.setVisibility(View.GONE);
+ infoButton.setManageVisibility(true);
+
+ DeleteZone deleteZone = (DeleteZone) menu.findItem(MENU_DELETE_APP).getActionView();
+ ((ViewGroup) deleteZone.getParent()).removeView(deleteZone);
+ mOrigDeleteZoneParent.addView(deleteZone, mOrigDeleteZoneLayoutParams);
+ deleteZone.setVisibility(View.GONE);
+ deleteZone.setManageVisibility(true);
+
+ mDragController.removeDropTarget(this);
+ endChoiceMode();
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ // This is never called. Because we use setActionView(), we handle our own click events.
+ return false;
+ }
+
+ /*
+ * We don't actually use AllAppsPagedView as a drop target... it's only used to intercept a drop
+ * to the workspace.
+ */
+ @Override
+ public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo) {
+ return false;
+ }
+ @Override
+ public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset,
+ int yOffset, DragView dragView, Object dragInfo) {
+ return null;
+ }
+ @Override
+ public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo) {}
+ @Override
+ public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo) {}
+ @Override
+ public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo) {}
+ @Override
+ public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo) {}
+
+ public boolean isDropEnabled() {
+ return true;
+ }
+}
diff --git a/src/com/android/launcher2/AllAppsTabbed.java b/src/com/android/launcher2/AllAppsTabbed.java
new file mode 100644
index 000000000..e0ff1a854
--- /dev/null
+++ b/src/com/android/launcher2/AllAppsTabbed.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import com.android.launcher.R;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.TabHost;
+
+import java.util.ArrayList;
+
+/**
+ * Implements a tabbed version of AllApps2D.
+ */
+public class AllAppsTabbed extends TabHost implements AllAppsView {
+
+ private static final String TAG = "Launcher.AllAppsTabbed";
+
+ private static final String TAG_ALL = "ALL";
+ private static final String TAG_DOWNLOADED = "DOWNLOADED";
+
+ private AllAppsPagedView mAllApps;
+ private Context mContext;
+
+ public AllAppsTabbed(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ // setup the tab host
+ setup();
+
+ try {
+ mAllApps = (AllAppsPagedView) findViewById(R.id.all_apps_paged_view);
+ if (mAllApps == null) throw new Resources.NotFoundException();
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "Can't find necessary layout elements for AllAppsTabbed");
+ }
+
+ // share the same AllApps workspace across all the tabs
+ TabContentFactory contentFactory = new TabContentFactory() {
+ public View createTabContent(String tag) {
+ return mAllApps;
+ }
+ };
+
+ String label = mContext.getString(R.string.all_apps_tab_all);
+ addTab(newTabSpec(TAG_ALL).setIndicator(label).setContent(contentFactory));
+
+ label = mContext.getString(R.string.all_apps_tab_downloaded);
+ addTab(newTabSpec(TAG_DOWNLOADED).setIndicator(label).setContent(contentFactory));
+
+ setOnTabChangedListener(new OnTabChangeListener() {
+ public void onTabChanged(String tabId) {
+ // animate the changing of the tab content by fading pages in and out
+ final int duration = 150;
+ final float alpha = mAllApps.getAlpha();
+ ValueAnimator alphaAnim = ObjectAnimator.ofFloat(mAllApps, "alpha", alpha, 0.0f).
+ setDuration(duration);
+ alphaAnim.addListener(new LauncherAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEndOrCancel(Animator animation) {
+ String tag = getCurrentTabTag();
+ if (tag == TAG_ALL) {
+ mAllApps.setAppFilter(AllAppsPagedView.ALL_APPS_FLAG);
+ } else if (tag == TAG_DOWNLOADED) {
+ mAllApps.setAppFilter(ApplicationInfo.DOWNLOADED_FLAG);
+ }
+
+ final float alpha = mAllApps.getAlpha();
+ ObjectAnimator.ofFloat(mAllApps, "alpha", alpha, 1.0f).
+ setDuration(duration).start();
+ }
+ });
+ alphaAnim.start();
+ }
+ });
+
+ // It needs to be INVISIBLE so that it will be measured in the layout.
+ // Otherwise the animations is messed up when we show it for the first time.
+ setVisibility(INVISIBLE);
+ }
+
+ @Override
+ public void setLauncher(Launcher launcher) {
+ mAllApps.setLauncher(launcher);
+ }
+
+ @Override
+ public void setDragController(DragController dragger) {
+ mAllApps.setDragController(dragger);
+ }
+
+ @Override
+ public void zoom(float zoom, boolean animate) {
+ // NOTE: animate parameter is ignored for the TabHost itself
+ setVisibility((zoom == 0.0f) ? View.GONE : View.VISIBLE);
+ mAllApps.zoom(zoom, animate);
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ final boolean isVisible = (visibility == View.VISIBLE);
+ super.setVisibility(visibility);
+ float zoom = (isVisible ? 1.0f : 0.0f);
+ mAllApps.zoom(zoom, false);
+ }
+
+ @Override
+ public boolean isVisible() {
+ return mAllApps.isVisible();
+ }
+
+ @Override
+ public boolean isAnimating() {
+ return (getAnimation() != null);
+ }
+
+ @Override
+ public void setApps(ArrayList<ApplicationInfo> list) {
+ mAllApps.setApps(list);
+ }
+
+ @Override
+ public void addApps(ArrayList<ApplicationInfo> list) {
+ mAllApps.addApps(list);
+ }
+
+ @Override
+ public void removeApps(ArrayList<ApplicationInfo> list) {
+ mAllApps.removeApps(list);
+ }
+
+ @Override
+ public void updateApps(ArrayList<ApplicationInfo> list) {
+ mAllApps.updateApps(list);
+ }
+
+ @Override
+ public void dumpState() {
+ mAllApps.dumpState();
+ }
+
+ @Override
+ public void surrender() {
+ mAllApps.surrender();
+ }
+}
diff --git a/src/com/android/launcher2/AllAppsView.java b/src/com/android/launcher2/AllAppsView.java
index 877c07577..007ecf8d6 100644
--- a/src/com/android/launcher2/AllAppsView.java
+++ b/src/com/android/launcher2/AllAppsView.java
@@ -31,7 +31,7 @@ public interface AllAppsView {
public boolean isVisible();
- public boolean isOpaque();
+ public boolean isAnimating();
public void setApps(ArrayList<ApplicationInfo> list);
diff --git a/src/com/android/launcher2/ApplicationInfo.java b/src/com/android/launcher2/ApplicationInfo.java
index 5bb503780..0851cd350 100644
--- a/src/com/android/launcher2/ApplicationInfo.java
+++ b/src/com/android/launcher2/ApplicationInfo.java
@@ -17,12 +17,11 @@
package com.android.launcher2;
import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
import android.util.Log;
import java.util.ArrayList;
@@ -31,6 +30,7 @@ import java.util.ArrayList;
* Represents an app in AllAppsView.
*/
class ApplicationInfo extends ItemInfo {
+ private static final String TAG = "Launcher2.ApplicationInfo";
/**
* The application name.
@@ -54,6 +54,10 @@ class ApplicationInfo extends ItemInfo {
ComponentName componentName;
+ static final int APP_FLAG = 1;
+ static final int GAME_FLAG = 2;
+ static final int DOWNLOADED_FLAG = 4;
+ int flags = 0;
ApplicationInfo() {
itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
@@ -62,15 +66,32 @@ class ApplicationInfo extends ItemInfo {
/**
* Must not hold the Context.
*/
- public ApplicationInfo(ResolveInfo info, IconCache iconCache) {
- this.componentName = new ComponentName(
- info.activityInfo.applicationInfo.packageName,
- info.activityInfo.name);
+ public ApplicationInfo(PackageManager pm, ResolveInfo info, IconCache iconCache) {
+ final String packageName = info.activityInfo.applicationInfo.packageName;
+ this.componentName = new ComponentName(packageName, info.activityInfo.name);
this.container = ItemInfo.NO_ID;
this.setActivity(componentName,
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ try {
+ int appFlags = pm.getApplicationInfo(packageName, 0).flags;
+ if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {
+ flags |= DOWNLOADED_FLAG;
+ }
+ if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+ flags |= DOWNLOADED_FLAG;
+ }
+ // TODO: Figure out how to determine what is a game
+
+ // If it's not a game, it's an app
+ if ((flags & GAME_FLAG) == 0) {
+ flags |= APP_FLAG;
+ }
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName);
+ }
+
iconCache.getTitleAndIcon(this, info);
}
@@ -79,6 +100,7 @@ class ApplicationInfo extends ItemInfo {
componentName = info.componentName;
title = info.title.toString();
intent = new Intent(info.intent);
+ flags = info.flags;
}
/**
diff --git a/src/com/android/launcher2/ApplicationInfoDropTarget.java b/src/com/android/launcher2/ApplicationInfoDropTarget.java
new file mode 100644
index 000000000..3e5ebd5ba
--- /dev/null
+++ b/src/com/android/launcher2/ApplicationInfoDropTarget.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+
+/**
+ * Implements a DropTarget which allows applications to be dropped on it,
+ * in order to launch the application info for that app.
+ */
+public class ApplicationInfoDropTarget extends ImageView implements DropTarget, DragController.DragListener {
+ private Launcher mLauncher;
+ private boolean mActive = false;
+
+ /**
+ * If true, this View responsible for managing its own visibility, and that of its handle.
+ * This is generally the case, but it will be set to false when this is part of the
+ * Contextual Action Bar.
+ */
+ private boolean mManageVisibility = true;
+
+ /** The view that this view should appear in the place of. */
+ private View mHandle = null;
+
+ private final Paint mPaint = new Paint();
+
+ public ApplicationInfoDropTarget(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ApplicationInfoDropTarget(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * Set the color that will be used as a filter over objects dragged over this object.
+ */
+ public void setDragColor(int color) {
+ mPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
+ }
+
+ public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo) {
+
+ // acceptDrop is called just before onDrop. We do the work here, rather than
+ // in onDrop, because it allows us to reject the drop (by returning false)
+ // so that the object being dragged isn't removed from the home screen.
+
+ ComponentName componentName = null;
+ if (dragInfo instanceof ApplicationInfo) {
+ componentName = ((ApplicationInfo)dragInfo).componentName;
+ } else if (dragInfo instanceof ShortcutInfo) {
+ componentName = ((ShortcutInfo)dragInfo).intent.getComponent();
+ }
+ mLauncher.startApplicationDetailsActivity(componentName);
+ return false;
+ }
+
+ public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo) {
+
+ }
+
+ public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo) {
+ dragView.setPaint(mPaint);
+ }
+
+ public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo) {
+ }
+
+ public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo) {
+ dragView.setPaint(null);
+ }
+
+ public void onDragStart(DragSource source, Object info, int dragAction) {
+ if (info != null) {
+ final int itemType = ((ItemInfo)info).itemType;
+ mActive = (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION);
+ if (mManageVisibility) {
+ // Only show the info icon when an application is selected
+ if (mActive) {
+ setVisibility(VISIBLE);
+ }
+ mHandle.setVisibility(INVISIBLE);
+ }
+ }
+ }
+
+ public boolean isDropEnabled() {
+ return mActive;
+ }
+
+ public void onDragEnd() {
+ if (mActive) {
+ mActive = false;
+ }
+ if (mManageVisibility) {
+ setVisibility(GONE);
+ mHandle.setVisibility(VISIBLE);
+ }
+ }
+
+ @Override
+ public void getHitRect(Rect outRect) {
+ super.getHitRect(outRect);
+ if (LauncherApplication.isScreenXLarge()) {
+ // TODO: This is a temporary hack. mManageVisiblity = false when you're in CAB mode.
+ // In that case, this icon is more tightly spaced next to the delete icon so we want
+ // it to have a smaller drag region. When the new drag&drop system comes in, we'll
+ // dispatch the drag/drop by calculating what target you're overlapping
+ final int dragPadding = mManageVisibility ? 50 : 10;
+ outRect.top -= dragPadding;
+ outRect.left -= dragPadding;
+ outRect.bottom += dragPadding;
+ outRect.right += dragPadding;
+ }
+ }
+
+ void setLauncher(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ void setHandle(View view) {
+ mHandle = view;
+ }
+
+ void setManageVisibility(boolean value) {
+ mManageVisibility = value;
+ }
+
+ @Override
+ public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo) {
+ return null;
+ }
+}
diff --git a/src/com/android/launcher2/BubbleTextView.java b/src/com/android/launcher2/BubbleTextView.java
index 4a56e1bb0..076f574de 100644
--- a/src/com/android/launcher2/BubbleTextView.java
+++ b/src/com/android/launcher2/BubbleTextView.java
@@ -144,4 +144,10 @@ public class BubbleTextView extends TextView {
super.onDetachedFromWindow();
mBackground.setCallback(null);
}
+
+ @Override
+ protected boolean onSetAlpha(int alpha) {
+ mPaint.setAlpha(alpha);
+ return super.onSetAlpha(alpha);
+ }
}
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index 9d39c2ca7..11c0c1822 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -16,54 +16,114 @@
package com.android.launcher2;
+import com.android.launcher.R;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.WallpaperManager;
import android.content.Context;
-import android.content.res.TypedArray;
import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.graphics.Canvas;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.ContextMenu;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
-import android.app.WallpaperManager;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LayoutAnimationController;
-import java.util.ArrayList;
+import java.util.Arrays;
-import com.android.launcher.R;
-
-public class CellLayout extends ViewGroup {
- private boolean mPortrait;
+public class CellLayout extends ViewGroup implements Dimmable {
+ static final String TAG = "CellLayout";
private int mCellWidth;
private int mCellHeight;
-
- private int mLongAxisStartPadding;
- private int mLongAxisEndPadding;
- private int mShortAxisStartPadding;
- private int mShortAxisEndPadding;
+ private int mLeftPadding;
+ private int mRightPadding;
+ private int mTopPadding;
+ private int mBottomPadding;
- private int mShortAxisCells;
- private int mLongAxisCells;
+ private int mCountX;
+ private int mCountY;
private int mWidthGap;
private int mHeightGap;
private final Rect mRect = new Rect();
+ private final RectF mRectF = new RectF();
private final CellInfo mCellInfo = new CellInfo();
-
- int[] mCellXY = new int[2];
+
+ // These are temporary variables to prevent having to allocate a new object just to
+ // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
+ private final int[] mTmpCellXY = new int[2];
+ private final int[] mTmpPoint = new int[2];
+ private final PointF mTmpPointF = new PointF();
+
boolean[][] mOccupied;
- private RectF mDragRect = new RectF();
+ private OnTouchListener mInterceptTouchListener;
+
+ private float mBackgroundAlpha;
+ private float mBackgroundAlphaMultiplier = 1.0f;
+
+ private Drawable mBackground;
+ private Drawable mBackgroundMini;
+ private Drawable mBackgroundMiniHover;
+ private Drawable mBackgroundHover;
+ private Drawable mBackgroundMiniAcceptsDrops;
+ private Rect mBackgroundRect;
+ private Rect mHoverRect;
+ private float mHoverScale;
+ private float mHoverAlpha;
+ private boolean mAcceptsDrops;
+
+ // If we're actively dragging something over this screen, mHover is true
+ private boolean mHover = false;
+
+ private final Point mDragCenter = new Point();
- private boolean mDirtyTag;
- private boolean mLastDownOnOccupiedCell = false;
-
- private final WallpaperManager mWallpaperManager;
+ // These arrays are used to implement the drag visualization on x-large screens.
+ // They are used as circular arrays, indexed by mDragOutlineCurrent.
+ private Point[] mDragOutlines = new Point[8];
+ private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
+ private InterruptibleInOutAnimator[] mDragOutlineAnims =
+ new InterruptibleInOutAnimator[mDragOutlines.length];
+
+ // Used as an index into the above 3 arrays; indicates which is the most current value.
+ private int mDragOutlineCurrent = 0;
+ private final Paint mDragOutlinePaint = new Paint();
+
+ private Drawable mCrosshairsDrawable = null;
+ private InterruptibleInOutAnimator mCrosshairsAnimator = null;
+ private float mCrosshairsVisibility = 0.0f;
+
+ // When a drag operation is in progress, holds the nearest cell to the touch point
+ private final int[] mDragCell = new int[2];
+
+ private final WallpaperManager mWallpaperManager;
+
+ private boolean mDragging = false;
+
+ private TimeInterpolator mEaseOutInterpolator;
public CellLayout(Context context) {
this(context, null);
@@ -75,44 +135,309 @@ public class CellLayout extends ViewGroup {
public CellLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+
+ // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
+ // the user where a dragged item will land when dropped.
+ setWillNotDraw(false);
+
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
-
- mLongAxisStartPadding =
- a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10);
- mLongAxisEndPadding =
- a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10);
- mShortAxisStartPadding =
- a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10);
- mShortAxisEndPadding =
- a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10);
-
- mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);
- mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4);
+ mWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, -1);
+ mHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, -1);
+
+ mLeftPadding =
+ a.getDimensionPixelSize(R.styleable.CellLayout_xAxisStartPadding, 10);
+ mRightPadding =
+ a.getDimensionPixelSize(R.styleable.CellLayout_xAxisEndPadding, 10);
+ mTopPadding =
+ a.getDimensionPixelSize(R.styleable.CellLayout_yAxisStartPadding, 10);
+ mBottomPadding =
+ a.getDimensionPixelSize(R.styleable.CellLayout_yAxisEndPadding, 10);
+
+ mCountX = LauncherModel.getCellCountX();
+ mCountY = LauncherModel.getCellCountY();
+ mOccupied = new boolean[mCountX][mCountY];
a.recycle();
setAlwaysDrawnWithCacheEnabled(false);
- if (mOccupied == null) {
- if (mPortrait) {
- mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
- } else {
- mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
+ mWallpaperManager = WallpaperManager.getInstance(context);
+
+ final Resources res = getResources();
+
+ if (LauncherApplication.isScreenXLarge()) {
+ mBackgroundMini = res.getDrawable(R.drawable.mini_home_screen_bg);
+ mBackgroundMini.setFilterBitmap(true);
+ mBackground = res.getDrawable(R.drawable.home_screen_bg);
+ mBackground.setFilterBitmap(true);
+ mBackgroundMiniHover = res.getDrawable(R.drawable.mini_home_screen_bg_hover);
+ mBackgroundMiniHover.setFilterBitmap(true);
+ mBackgroundHover = res.getDrawable(R.drawable.home_screen_bg_hover);
+ mBackgroundHover.setFilterBitmap(true);
+ mBackgroundMiniAcceptsDrops = res.getDrawable(
+ R.drawable.mini_home_screen_bg_accepts_drops);
+ mBackgroundMiniAcceptsDrops.setFilterBitmap(true);
+ }
+
+ // Initialize the data structures used for the drag visualization.
+
+ mCrosshairsDrawable = res.getDrawable(R.drawable.gardening_crosshairs);
+ mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
+
+ // Set up the animation for fading the crosshairs in and out
+ int animDuration = res.getInteger(R.integer.config_crosshairsFadeInTime);
+ mCrosshairsAnimator = new InterruptibleInOutAnimator(animDuration, 0.0f, 1.0f);
+ mCrosshairsAnimator.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mCrosshairsVisibility = ((Float) animation.getAnimatedValue()).floatValue();
+ invalidate();
}
+ });
+ mCrosshairsAnimator.getAnimator().setInterpolator(mEaseOutInterpolator);
+
+ for (int i = 0; i < mDragOutlines.length; i++) {
+ mDragOutlines[i] = new Point(-1, -1);
}
-
- mWallpaperManager = WallpaperManager.getInstance(getContext());
+
+ // When dragging things around the home screens, we show a green outline of
+ // where the item will land. The outlines gradually fade out, leaving a trail
+ // behind the drag path.
+ // Set up all the animations that are used to implement this fading.
+ final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
+ final float fromAlphaValue = 0;
+ final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
+
+ Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
+
+ for (int i = 0; i < mDragOutlineAnims.length; i++) {
+ final InterruptibleInOutAnimator anim =
+ new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
+ anim.getAnimator().setInterpolator(mEaseOutInterpolator);
+ final int thisIndex = i;
+ anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final Bitmap outline = (Bitmap)anim.getTag();
+
+ // If an animation is started and then stopped very quickly, we can still
+ // get spurious updates we've cleared the tag. Guard against this.
+ if (outline == null) {
+ if (false) {
+ Object val = animation.getAnimatedValue();
+ Log.d(TAG, "anim " + thisIndex + " update: " + val +
+ ", isStopped " + anim.isStopped());
+ }
+ // Try to prevent it from continuing to run
+ animation.cancel();
+ } else {
+ mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
+ final int left = mDragOutlines[thisIndex].x;
+ final int top = mDragOutlines[thisIndex].y;
+ CellLayout.this.invalidate(left, top,
+ left + outline.getWidth(), top + outline.getHeight());
+ }
+ }
+ });
+ // The animation holds a reference to the drag outline bitmap as long is it's
+ // running. This way the bitmap can be GCed when the animations are complete.
+ anim.getAnimator().addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
+ anim.setTag(null);
+ }
+ }
+ });
+ mDragOutlineAnims[i] = anim;
+ }
+
+ mBackgroundRect = new Rect();
+ mHoverRect = new Rect();
+ setHoverScale(1.0f);
+ setHoverAlpha(1.0f);
}
- @Override
- public void dispatchDraw(Canvas canvas) {
+ private void updateHoverRect() {
+ float marginFraction = (mHoverScale - 1.0f) / 2.0f;
+ int marginX = (int) (marginFraction * (mBackgroundRect.right - mBackgroundRect.left));
+ int marginY = (int) (marginFraction * (mBackgroundRect.bottom - mBackgroundRect.top));
+ mHoverRect.set(mBackgroundRect.left - marginX, mBackgroundRect.top - marginY,
+ mBackgroundRect.right + marginX, mBackgroundRect.bottom + marginY);
+ invalidate();
+ }
+
+ public void setHoverScale(float scaleFactor) {
+ if (scaleFactor != mHoverScale) {
+ mHoverScale = scaleFactor;
+ updateHoverRect();
+ }
+ }
+
+ public float getHoverScale() {
+ return mHoverScale;
+ }
+
+ public float getHoverAlpha() {
+ return mHoverAlpha;
+ }
+
+ public void setHoverAlpha(float alpha) {
+ mHoverAlpha = alpha;
+ invalidate();
+ }
+
+ void animateDrop() {
+ if (LauncherApplication.isScreenXLarge()) {
+ Resources res = getResources();
+ float onDropScale = res.getInteger(R.integer.config_screenOnDropScalePercent) / 100.0f;
+ ObjectAnimator scaleUp = ObjectAnimator.ofFloat(this, "hoverScale", onDropScale);
+ scaleUp.setDuration(res.getInteger(R.integer.config_screenOnDropScaleUpDuration));
+ ObjectAnimator scaleDown = ObjectAnimator.ofFloat(this, "hoverScale", 1.0f);
+ scaleDown.setDuration(res.getInteger(R.integer.config_screenOnDropScaleDownDuration));
+ ObjectAnimator alphaFadeOut = ObjectAnimator.ofFloat(this, "hoverAlpha", 0.0f);
+
+ alphaFadeOut.setStartDelay(res.getInteger(R.integer.config_screenOnDropAlphaFadeDelay));
+ alphaFadeOut.setDuration(res.getInteger(R.integer.config_screenOnDropAlphaFadeDelay));
+
+ AnimatorSet bouncer = new AnimatorSet();
+ bouncer.play(scaleUp).before(scaleDown);
+ bouncer.play(scaleUp).with(alphaFadeOut);
+ bouncer.addListener(new LauncherAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ setHover(true);
+ }
+ @Override
+ public void onAnimationEndOrCancel(Animator animation) {
+ setHover(false);
+ setHoverScale(1.0f);
+ setHoverAlpha(1.0f);
+ }
+ });
+ bouncer.start();
+ }
+ }
+
+ public void setHover(boolean value) {
+ if (mHover != value) {
+ mHover = value;
+ invalidate();
+ }
+ }
+
+ public boolean getHover() {
+ return mHover;
+ }
+
+ public void drawChildren(Canvas canvas) {
super.dispatchDraw(canvas);
}
@Override
+ protected void onDraw(Canvas canvas) {
+ // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
+ // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
+ // When we're small, we are either drawn normally or in the "accepts drops" state (during
+ // a drag). However, we also drag the mini hover background *over* one of those two
+ // backgrounds
+ if (mBackgroundAlpha > 0.0f) {
+ Drawable bg;
+ if (getScaleX() < 0.5f) {
+ bg = mAcceptsDrops ? mBackgroundMiniAcceptsDrops : mBackgroundMini;
+ } else {
+ bg = mHover ? mBackgroundHover : mBackground;
+ }
+ if (bg != null) {
+ bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
+ bg.setBounds(mBackgroundRect);
+ bg.draw(canvas);
+ }
+ if (mHover && getScaleX() < 0.5f) {
+ boolean modifiedClipRect = false;
+ if (mHoverScale > 1.0f) {
+ // If the hover background's scale is greater than 1, we'll be drawing outside
+ // the bounds of this CellLayout. Get around that by temporarily increasing the
+ // size of the clip rect
+ float marginFraction = (mHoverScale - 1.0f) / 2.0f;
+ Rect clipRect = canvas.getClipBounds();
+ int marginX = (int) (marginFraction * (clipRect.right - clipRect.left));
+ int marginY = (int) (marginFraction * (clipRect.bottom - clipRect.top));
+ canvas.save(Canvas.CLIP_SAVE_FLAG);
+ canvas.clipRect(-marginX, -marginY,
+ getWidth() + marginX, getHeight() + marginY, Region.Op.REPLACE);
+ modifiedClipRect = true;
+ }
+
+ mBackgroundMiniHover.setAlpha((int) (mBackgroundAlpha * mHoverAlpha * 255));
+ mBackgroundMiniHover.setBounds(mHoverRect);
+ mBackgroundMiniHover.draw(canvas);
+ if (modifiedClipRect) {
+ canvas.restore();
+ }
+ }
+ }
+
+ if (mCrosshairsVisibility > 0.0f) {
+ final int countX = mCountX;
+ final int countY = mCountY;
+
+ final float MAX_ALPHA = 0.4f;
+ final int MAX_VISIBLE_DISTANCE = 600;
+ final float DISTANCE_MULTIPLIER = 0.002f;
+
+ final Drawable d = mCrosshairsDrawable;
+ final int width = d.getIntrinsicWidth();
+ final int height = d.getIntrinsicHeight();
+
+ int x = getLeftPadding() - (mWidthGap / 2) - (width / 2);
+ for (int col = 0; col <= countX; col++) {
+ int y = getTopPadding() - (mHeightGap / 2) - (height / 2);
+ for (int row = 0; row <= countY; row++) {
+ mTmpPointF.set(x - mDragCenter.x, y - mDragCenter.y);
+ float dist = mTmpPointF.length();
+ // Crosshairs further from the drag point are more faint
+ float alpha = Math.min(MAX_ALPHA,
+ DISTANCE_MULTIPLIER * (MAX_VISIBLE_DISTANCE - dist));
+ if (alpha > 0.0f) {
+ d.setBounds(x, y, x + width, y + height);
+ d.setAlpha((int) (alpha * 255 * mCrosshairsVisibility));
+ d.draw(canvas);
+ }
+ y += mCellHeight + mHeightGap;
+ }
+ x += mCellWidth + mWidthGap;
+ }
+ }
+
+ final Paint paint = mDragOutlinePaint;
+ for (int i = 0; i < mDragOutlines.length; i++) {
+ final float alpha = mDragOutlineAlphas[i];
+ if (alpha > 0) {
+ final Point p = mDragOutlines[i];
+ final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
+ paint.setAlpha((int)(alpha + .5f));
+ canvas.drawBitmap(b, p.x, p.y, paint);
+ }
+ }
+ }
+
+ public void setDimmableProgress(float progress) {
+ for (int i = 0; i < getChildCount(); i++) {
+ Dimmable d = (Dimmable) getChildAt(i);
+ d.setDimmableProgress(progress);
+ }
+ }
+
+ public float getDimmableProgress() {
+ if (getChildCount() > 0) {
+ return ((Dimmable) getChildAt(0)).getDimmableProgress();
+ }
+ return 0.0f;
+ }
+
+ @Override
public void cancelLongPress() {
super.cancelLongPress();
@@ -124,22 +449,104 @@ public class CellLayout extends ViewGroup {
}
}
+ public void setOnInterceptTouchListener(View.OnTouchListener listener) {
+ mInterceptTouchListener = listener;
+ }
+
int getCountX() {
- return mPortrait ? mShortAxisCells : mLongAxisCells;
+ return mCountX;
}
int getCountY() {
- return mPortrait ? mLongAxisCells : mShortAxisCells;
+ return mCountY;
}
- @Override
- public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params) {
+ return addViewToCellLayout(child, index, childId, params, true);
+ }
+
+ public boolean addViewToCellLayout(
+ View child, int index, int childId, LayoutParams params, boolean markCells) {
+ final LayoutParams lp = params;
+
// Generate an id for each view, this assumes we have at most 256x256 cells
// per workspace screen
- final LayoutParams cellParams = (LayoutParams) params;
- cellParams.regenerateId = true;
+ if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
+ // If the horizontal or vertical span is set to -1, it is taken to
+ // mean that it spans the extent of the CellLayout
+ if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
+ if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
+
+ child.setId(childId);
+
+ // We might be in the middle or end of shrinking/fading to a dimmed view
+ // Make sure this view's alpha is set the same as all the rest of the views
+ child.setAlpha(getAlpha());
+ addView(child, index, lp);
+
+ if (markCells) markCellsAsOccupiedForView(child);
+
+ return true;
+ }
+ return false;
+ }
+ public void setAcceptsDrops(boolean acceptsDrops) {
+ if (mAcceptsDrops != acceptsDrops) {
+ mAcceptsDrops = acceptsDrops;
+ invalidate();
+ }
+ }
+
+ public boolean getAcceptsDrops() {
+ return mAcceptsDrops;
+ }
+
+ @Override
+ public void removeAllViews() {
+ clearOccupiedCells();
+ }
+
+ @Override
+ public void removeAllViewsInLayout() {
+ clearOccupiedCells();
+ }
+
+ public void removeViewWithoutMarkingCells(View view) {
+ super.removeView(view);
+ }
+
+ @Override
+ public void removeView(View view) {
+ markCellsAsUnoccupiedForView(view);
+ super.removeView(view);
+ }
+
+ @Override
+ public void removeViewAt(int index) {
+ markCellsAsUnoccupiedForView(getChildAt(index));
+ super.removeViewAt(index);
+ }
+
+ @Override
+ public void removeViewInLayout(View view) {
+ markCellsAsUnoccupiedForView(view);
+ super.removeViewInLayout(view);
+ }
- super.addView(child, index, params);
+ @Override
+ public void removeViews(int start, int count) {
+ for (int i = start; i < start + count; i++) {
+ markCellsAsUnoccupiedForView(getChildAt(i));
+ }
+ super.removeViews(start, count);
+ }
+
+ @Override
+ public void removeViewsInLayout(int start, int count) {
+ for (int i = start; i < start + count; i++) {
+ markCellsAsUnoccupiedForView(getChildAt(i));
+ }
+ super.removeViewsInLayout(start, count);
}
@Override
@@ -158,67 +565,59 @@ public class CellLayout extends ViewGroup {
mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
}
+ public void setTagToCellInfoForPoint(int touchX, int touchY) {
+ final CellInfo cellInfo = mCellInfo;
+ final Rect frame = mRect;
+ final int x = touchX + mScrollX;
+ final int y = touchY + mScrollY;
+ final int count = getChildCount();
+
+ boolean found = false;
+ for (int i = count - 1; i >= 0; i--) {
+ final View child = getChildAt(i);
+
+ if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
+ child.getHitRect(frame);
+ if (frame.contains(x, y)) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ cellInfo.cell = child;
+ cellInfo.cellX = lp.cellX;
+ cellInfo.cellY = lp.cellY;
+ cellInfo.spanX = lp.cellHSpan;
+ cellInfo.spanY = lp.cellVSpan;
+ cellInfo.valid = true;
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ final int cellXY[] = mTmpCellXY;
+ pointToCellExact(x, y, cellXY);
+
+ cellInfo.cell = null;
+ cellInfo.cellX = cellXY[0];
+ cellInfo.cellY = cellXY[1];
+ cellInfo.spanX = 1;
+ cellInfo.spanY = 1;
+ cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < mCountX &&
+ cellXY[1] < mCountY && !mOccupied[cellXY[0]][cellXY[1]];
+ }
+ setTag(cellInfo);
+ }
+
+
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
+ return true;
+ }
final int action = ev.getAction();
final CellInfo cellInfo = mCellInfo;
if (action == MotionEvent.ACTION_DOWN) {
- final Rect frame = mRect;
- final int x = (int) ev.getX() + mScrollX;
- final int y = (int) ev.getY() + mScrollY;
- final int count = getChildCount();
-
- boolean found = false;
- for (int i = count - 1; i >= 0; i--) {
- final View child = getChildAt(i);
-
- if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
- child.getHitRect(frame);
- if (frame.contains(x, y)) {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- cellInfo.cell = child;
- cellInfo.cellX = lp.cellX;
- cellInfo.cellY = lp.cellY;
- cellInfo.spanX = lp.cellHSpan;
- cellInfo.spanY = lp.cellVSpan;
- cellInfo.valid = true;
- found = true;
- mDirtyTag = false;
- break;
- }
- }
- }
-
- mLastDownOnOccupiedCell = found;
-
- if (!found) {
- int cellXY[] = mCellXY;
- pointToCellExact(x, y, cellXY);
-
- final boolean portrait = mPortrait;
- final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
- final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
-
- final boolean[][] occupied = mOccupied;
- findOccupiedCells(xCount, yCount, occupied, null);
-
- cellInfo.cell = null;
- cellInfo.cellX = cellXY[0];
- cellInfo.cellY = cellXY[1];
- cellInfo.spanX = 1;
- cellInfo.spanY = 1;
- cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
- cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
-
- // Instead of finding the interesting vacant cells here, wait until a
- // caller invokes getTag() to retrieve the result. Finding the vacant
- // cells is a bit expensive and can generate many new objects, it's
- // therefore better to defer it until we know we actually need it.
-
- mDirtyTag = true;
- }
- setTag(cellInfo);
+ setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
} else if (action == MotionEvent.ACTION_UP) {
cellInfo.cell = null;
cellInfo.cellX = -1;
@@ -226,7 +625,6 @@ public class CellLayout extends ViewGroup {
cellInfo.spanX = 0;
cellInfo.spanY = 0;
cellInfo.valid = false;
- mDirtyTag = false;
setTag(cellInfo);
}
@@ -235,104 +633,12 @@ public class CellLayout extends ViewGroup {
@Override
public CellInfo getTag() {
- final CellInfo info = (CellInfo) super.getTag();
- if (mDirtyTag && info.valid) {
- final boolean portrait = mPortrait;
- final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
- final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
-
- final boolean[][] occupied = mOccupied;
- findOccupiedCells(xCount, yCount, occupied, null);
-
- findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
-
- mDirtyTag = false;
- }
- return info;
- }
-
- private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y,
- int xCount, int yCount, boolean[][] occupied) {
-
- cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
- cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
- cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
- cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
- cellInfo.clearVacantCells();
-
- if (occupied[x][y]) {
- return;
- }
-
- cellInfo.current.set(x, y, x, y);
-
- findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo);
- }
-
- private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
- CellInfo cellInfo) {
-
- addVacantCell(current, cellInfo);
-
- if (current.left > 0) {
- if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
- current.left--;
- findVacantCell(current, xCount, yCount, occupied, cellInfo);
- current.left++;
- }
- }
-
- if (current.right < xCount - 1) {
- if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
- current.right++;
- findVacantCell(current, xCount, yCount, occupied, cellInfo);
- current.right--;
- }
- }
-
- if (current.top > 0) {
- if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
- current.top--;
- findVacantCell(current, xCount, yCount, occupied, cellInfo);
- current.top++;
- }
- }
-
- if (current.bottom < yCount - 1) {
- if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
- current.bottom++;
- findVacantCell(current, xCount, yCount, occupied, cellInfo);
- current.bottom--;
- }
- }
- }
-
- private static void addVacantCell(Rect current, CellInfo cellInfo) {
- CellInfo.VacantCell cell = CellInfo.VacantCell.acquire();
- cell.cellX = current.left;
- cell.cellY = current.top;
- cell.spanX = current.right - current.left + 1;
- cell.spanY = current.bottom - current.top + 1;
- if (cell.spanX > cellInfo.maxVacantSpanX) {
- cellInfo.maxVacantSpanX = cell.spanX;
- cellInfo.maxVacantSpanXSpanY = cell.spanY;
- }
- if (cell.spanY > cellInfo.maxVacantSpanY) {
- cellInfo.maxVacantSpanY = cell.spanY;
- cellInfo.maxVacantSpanYSpanX = cell.spanX;
- }
- cellInfo.vacantCells.add(cell);
- }
-
- private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
- for (int y = top; y <= bottom; y++) {
- if (occupied[x][y]) {
- return false;
- }
- }
- return true;
+ return (CellInfo) super.getTag();
}
+ /**
+ * Check if the row 'y' is empty from columns 'left' to 'right', inclusive.
+ */
private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
for (int x = left; x <= right; x++) {
if (occupied[x][y]) {
@@ -342,79 +648,28 @@ public class CellLayout extends ViewGroup {
return true;
}
- CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
- final boolean portrait = mPortrait;
- final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
- final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
-
- boolean[][] occupied = mOccupied;
-
- if (occupiedCells != null) {
- for (int y = 0; y < yCount; y++) {
- for (int x = 0; x < xCount; x++) {
- occupied[x][y] = occupiedCells[y * xCount + x];
- }
- }
- } else {
- findOccupiedCells(xCount, yCount, occupied, ignoreView);
- }
-
- CellInfo cellInfo = new CellInfo();
-
- cellInfo.cellX = -1;
- cellInfo.cellY = -1;
- cellInfo.spanY = 0;
- cellInfo.spanX = 0;
- cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
- cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
- cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
- cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
- cellInfo.screen = mCellInfo.screen;
-
- Rect current = cellInfo.current;
-
- for (int x = 0; x < xCount; x++) {
- for (int y = 0; y < yCount; y++) {
- if (!occupied[x][y]) {
- current.set(x, y, x, y);
- findVacantCell(current, xCount, yCount, occupied, cellInfo);
- occupied[x][y] = true;
- }
- }
- }
-
- cellInfo.valid = cellInfo.vacantCells.size() > 0;
-
- // Assume the caller will perform their own cell searching, otherwise we
- // risk causing an unnecessary rebuild after findCellForSpan()
-
- return cellInfo;
- }
-
/**
- * Given a point, return the cell that strictly encloses that point
+ * Given a point, return the cell that strictly encloses that point
* @param x X coordinate of the point
* @param y Y coordinate of the point
* @param result Array of 2 ints to hold the x and y coordinate of the cell
*/
void pointToCellExact(int x, int y, int[] result) {
- final boolean portrait = mPortrait;
-
- final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
- final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
+ final int hStartPadding = getLeftPadding();
+ final int vStartPadding = getTopPadding();
result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
- final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
- final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
+ final int xAxis = mCountX;
+ final int yAxis = mCountY;
if (result[0] < 0) result[0] = 0;
if (result[0] >= xAxis) result[0] = xAxis - 1;
if (result[1] < 0) result[1] = 0;
if (result[1] >= yAxis) result[1] = yAxis - 1;
}
-
+
/**
* Given a point, return the cell that most closely encloses that point
* @param x X coordinate of the point
@@ -427,18 +682,15 @@ public class CellLayout extends ViewGroup {
/**
* Given a cell coordinate, return the point that represents the upper left corner of that cell
- *
- * @param cellX X coordinate of the cell
+ *
+ * @param cellX X coordinate of the cell
* @param cellY Y coordinate of the cell
- *
+ *
* @param result Array of 2 ints to hold the x and y coordinate of the point
*/
void cellToPoint(int cellX, int cellY, int[] result) {
- final boolean portrait = mPortrait;
-
- final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
- final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
-
+ final int hStartPadding = getLeftPadding();
+ final int vStartPadding = getTopPadding();
result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
@@ -453,101 +705,76 @@ public class CellLayout extends ViewGroup {
}
int getLeftPadding() {
- return mPortrait ? mShortAxisStartPadding : mLongAxisStartPadding;
+ return mLeftPadding;
}
int getTopPadding() {
- return mPortrait ? mLongAxisStartPadding : mShortAxisStartPadding;
+ return mTopPadding;
}
int getRightPadding() {
- return mPortrait ? mShortAxisEndPadding : mLongAxisEndPadding;
+ return mRightPadding;
}
int getBottomPadding() {
- return mPortrait ? mLongAxisEndPadding : mShortAxisEndPadding;
+ return mBottomPadding;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO: currently ignoring padding
-
+
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
- int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
-
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
-
+
if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
}
- final int shortAxisCells = mShortAxisCells;
- final int longAxisCells = mLongAxisCells;
- final int longAxisStartPadding = mLongAxisStartPadding;
- final int longAxisEndPadding = mLongAxisEndPadding;
- final int shortAxisStartPadding = mShortAxisStartPadding;
- final int shortAxisEndPadding = mShortAxisEndPadding;
final int cellWidth = mCellWidth;
final int cellHeight = mCellHeight;
- mPortrait = heightSpecSize > widthSpecSize;
+ int numWidthGaps = mCountX - 1;
+ int numHeightGaps = mCountY - 1;
- int numShortGaps = shortAxisCells - 1;
- int numLongGaps = longAxisCells - 1;
+ if (mWidthGap < 0 || mHeightGap < 0) {
+ int vSpaceLeft = heightSpecSize - mTopPadding - mBottomPadding - (cellHeight * mCountY);
+ mHeightGap = vSpaceLeft / numHeightGaps;
- if (mPortrait) {
- int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding
- - (cellHeight * longAxisCells);
- mHeightGap = vSpaceLeft / numLongGaps;
+ int hSpaceLeft = widthSpecSize - mLeftPadding - mRightPadding - (cellWidth * mCountX);
+ mWidthGap = hSpaceLeft / numWidthGaps;
- int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding
- - (cellWidth * shortAxisCells);
- if (numShortGaps > 0) {
- mWidthGap = hSpaceLeft / numShortGaps;
- } else {
- mWidthGap = 0;
- }
- } else {
- int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding
- - (cellWidth * longAxisCells);
- mWidthGap = hSpaceLeft / numLongGaps;
-
- int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding
- - (cellHeight * shortAxisCells);
- if (numShortGaps > 0) {
- mHeightGap = vSpaceLeft / numShortGaps;
- } else {
- mHeightGap = 0;
- }
+ // center it around the min gaps
+ int minGap = Math.min(mWidthGap, mHeightGap);
+ mWidthGap = mHeightGap = minGap;
}
-
+
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
- if (mPortrait) {
- lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding,
- longAxisStartPadding);
- } else {
- lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding,
- shortAxisStartPadding);
- }
-
- if (lp.regenerateId) {
- child.setId(((getId() & 0xFF) << 16) | (lp.cellX & 0xFF) << 8 | (lp.cellY & 0xFF));
- lp.regenerateId = false;
- }
+ lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap,
+ mLeftPadding, mTopPadding);
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
- int childheightMeasureSpec =
- MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
+ int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
+ MeasureSpec.EXACTLY);
+
child.measure(childWidthMeasureSpec, childheightMeasureSpec);
}
-
- setMeasuredDimension(widthSpecSize, heightSpecSize);
+ if (widthSpecMode == MeasureSpec.AT_MOST) {
+ int newWidth = mLeftPadding + mRightPadding + (mCountX * cellWidth) +
+ ((mCountX - 1) * mWidthGap);
+ int newHeight = mTopPadding + mBottomPadding + (mCountY * cellHeight) +
+ ((mCountY - 1) * mHeightGap);
+ setMeasuredDimension(newWidth, newHeight);
+ } else if (widthSpecMode == MeasureSpec.EXACTLY) {
+ setMeasuredDimension(widthSpecSize, heightSpecSize);
+ }
}
@Override
@@ -555,7 +782,7 @@ public class CellLayout extends ViewGroup {
int count = getChildCount();
for (int i = 0; i < count; i++) {
- View child = getChildAt(i);
+ final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
@@ -567,24 +794,35 @@ public class CellLayout extends ViewGroup {
if (lp.dropped) {
lp.dropped = false;
- final int[] cellXY = mCellXY;
+ final int[] cellXY = mTmpCellXY;
getLocationOnScreen(cellXY);
mWallpaperManager.sendWallpaperCommand(getWindowToken(), "android.home.drop",
cellXY[0] + childLeft + lp.width / 2,
cellXY[1] + childTop + lp.height / 2, 0, null);
+
+ ((Workspace) mParent).animateViewIntoPosition(child);
}
}
}
}
@Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mBackgroundRect.set(0, 0, w, h);
+ updateHoverRect();
+ }
+
+ @Override
protected void setChildrenDrawingCacheEnabled(boolean enabled) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View view = getChildAt(i);
view.setDrawingCacheEnabled(enabled);
// Update the drawing caches
- view.buildDrawingCache(true);
+ if (!view.isHardwareAccelerated()) {
+ view.buildDrawingCache(true);
+ }
}
}
@@ -593,154 +831,472 @@ public class CellLayout extends ViewGroup {
super.setChildrenDrawnWithCacheEnabled(enabled);
}
+ public float getBackgroundAlpha() {
+ return mBackgroundAlpha;
+ }
+
+ public void setBackgroundAlphaMultiplier(float multiplier) {
+ mBackgroundAlphaMultiplier = multiplier;
+ }
+
+ public void setBackgroundAlpha(float alpha) {
+ mBackgroundAlpha = alpha;
+ invalidate();
+ }
+
+ // Need to return true to let the view system know we know how to handle alpha-- this is
+ // because when our children have an alpha of 0.0f, they are still rendering their "dimmed"
+ // versions
+ @Override
+ protected boolean onSetAlpha(int alpha) {
+ return true;
+ }
+
+ public void setAlpha(float alpha) {
+ setChildrenAlpha(alpha);
+ super.setAlpha(alpha);
+ }
+
+ private void setChildrenAlpha(float alpha) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ getChildAt(i).setAlpha(alpha);
+ }
+ }
+
+ private boolean isVacantIgnoring(
+ int originX, int originY, int spanX, int spanY, View ignoreView) {
+ if (ignoreView != null) {
+ markCellsAsUnoccupiedForView(ignoreView);
+ }
+ boolean isVacant = true;
+ for (int i = 0; i < spanY; i++) {
+ if (!isRowEmpty(originY + i, originX, originX + spanX - 1, mOccupied)) {
+ isVacant = false;
+ break;
+ }
+ }
+ if (ignoreView != null) {
+ markCellsAsOccupiedForView(ignoreView);
+ }
+ return isVacant;
+ }
+
+ private boolean isVacant(int originX, int originY, int spanX, int spanY) {
+ return isVacantIgnoring(originX, originY, spanX, spanY, null);
+ }
+
+ public View getChildAt(int x, int y) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
+ (lp.cellY <= y) && (y < lp.cellY + lp.cellHSpan)) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Estimate the size that a child with the given dimensions will take in the layout.
+ */
+ void estimateChildSize(int minWidth, int minHeight, int[] result) {
+ // Assuming it's placed at 0, 0, find where the bottom right cell will land
+ rectToCell(minWidth, minHeight, result);
+
+ // Then figure out the rect it will occupy
+ cellToRect(0, 0, result[0], result[1], mRectF);
+ result[0] = (int)mRectF.width();
+ result[1] = (int)mRectF.height();
+ }
+
+ /**
+ * Estimate where the top left cell of the dragged item will land if it is dropped.
+ *
+ * @param originX The X value of the top left corner of the item
+ * @param originY The Y value of the top left corner of the item
+ * @param spanX The number of horizontal cells that the item spans
+ * @param spanY The number of vertical cells that the item spans
+ * @param result The estimated drop cell X and Y.
+ */
+ void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
+ final int countX = mCountX;
+ final int countY = mCountY;
+
+ // pointToCellRounded takes the top left of a cell but will pad that with
+ // cellWidth/2 and cellHeight/2 when finding the matching cell
+ pointToCellRounded(originX, originY, result);
+
+ // If the item isn't fully on this screen, snap to the edges
+ int rightOverhang = result[0] + spanX - countX;
+ if (rightOverhang > 0) {
+ result[0] -= rightOverhang; // Snap to right
+ }
+ result[0] = Math.max(0, result[0]); // Snap to left
+ int bottomOverhang = result[1] + spanY - countY;
+ if (bottomOverhang > 0) {
+ result[1] -= bottomOverhang; // Snap to bottom
+ }
+ result[1] = Math.max(0, result[1]); // Snap to top
+ }
+
+ void visualizeDropLocation(
+ View v, Bitmap dragOutline, int originX, int originY, int spanX, int spanY) {
+
+ final int oldDragCellX = mDragCell[0];
+ final int oldDragCellY = mDragCell[1];
+ final int[] nearest = findNearestVacantArea(originX, originY, spanX, spanY, v, mDragCell);
+ if (v != null) {
+ mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
+ } else {
+ mDragCenter.set(originX, originY);
+ }
+
+ if (nearest != null && (nearest[0] != oldDragCellX || nearest[1] != oldDragCellY)) {
+ // Find the top left corner of the rect the object will occupy
+ final int[] topLeft = mTmpPoint;
+ cellToPoint(nearest[0], nearest[1], topLeft);
+
+ int left = topLeft[0];
+ int top = topLeft[1];
+
+ if (v != null) {
+ if (v.getParent() instanceof CellLayout) {
+ LayoutParams lp = (LayoutParams) v.getLayoutParams();
+ left += lp.leftMargin;
+ top += lp.topMargin;
+ }
+
+ // Offsets due to the size difference between the View and the dragOutline
+ left += (v.getWidth() - dragOutline.getWidth()) / 2;
+ top += (v.getHeight() - dragOutline.getHeight()) / 2;
+ }
+
+ final int oldIndex = mDragOutlineCurrent;
+ mDragOutlineAnims[oldIndex].animateOut();
+ mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
+
+ mDragOutlines[mDragOutlineCurrent].set(left, top);
+ mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
+ mDragOutlineAnims[mDragOutlineCurrent].animateIn();
+ }
+
+ // If we are drawing crosshairs, the entire CellLayout needs to be invalidated
+ if (mCrosshairsDrawable != null) {
+ invalidate();
+ }
+ }
+
+ /**
+ * Find a vacant area that will fit the given bounds nearest the requested
+ * cell location. Uses Euclidean distance to score multiple vacant areas.
+ *
+ * @param pixelX The X location at which you want to search for a vacant area.
+ * @param pixelY The Y location at which you want to search for a vacant area.
+ * @param spanX Horizontal span of the object.
+ * @param spanY Vertical span of the object.
+ * @param result Array in which to place the result, or null (in which case a new array will
+ * be allocated)
+ * @return The X, Y cell of a vacant area that can contain this object,
+ * nearest the requested location.
+ */
+ int[] findNearestVacantArea(
+ int pixelX, int pixelY, int spanX, int spanY, int[] result) {
+ return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
+ }
+
/**
* Find a vacant area that will fit the given bounds nearest the requested
* cell location. Uses Euclidean distance to score multiple vacant areas.
- *
+ *
* @param pixelX The X location at which you want to search for a vacant area.
* @param pixelY The Y location at which you want to search for a vacant area.
* @param spanX Horizontal span of the object.
* @param spanY Vertical span of the object.
- * @param vacantCells Pre-computed set of vacant cells to search.
- * @param recycle Previously returned value to possibly recycle.
+ * @param ignoreView Considers space occupied by this view as unoccupied
+ * @param result Previously returned value to possibly recycle.
* @return The X, Y cell of a vacant area that can contain this object,
* nearest the requested location.
*/
- int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
- CellInfo vacantCells, int[] recycle) {
-
+ int[] findNearestVacantArea(
+ int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
+ // mark space take by ignoreView as available (method checks if ignoreView is null)
+ markCellsAsUnoccupiedForView(ignoreView);
+
// Keep track of best-scoring drop area
- final int[] bestXY = recycle != null ? recycle : new int[2];
- final int[] cellXY = mCellXY;
+ final int[] bestXY = result != null ? result : new int[2];
double bestDistance = Double.MAX_VALUE;
-
- // Bail early if vacant cells aren't valid
- if (!vacantCells.valid) {
- return null;
- }
- // Look across all vacant cells for best fit
- final int size = vacantCells.vacantCells.size();
- for (int i = 0; i < size; i++) {
- final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i);
-
- // Reject if vacant cell isn't our exact size
- if (cell.spanX != spanX || cell.spanY != spanY) {
- continue;
- }
-
- // Score is center distance from requested pixel
- cellToPoint(cell.cellX, cell.cellY, cellXY);
-
- double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) +
- Math.pow(cellXY[1] - pixelY, 2));
- if (distance <= bestDistance) {
- bestDistance = distance;
- bestXY[0] = cell.cellX;
- bestXY[1] = cell.cellY;
+ final int countX = mCountX;
+ final int countY = mCountY;
+ final boolean[][] occupied = mOccupied;
+
+ for (int x = 0; x < countX - (spanX - 1); x++) {
+ inner:
+ for (int y = 0; y < countY - (spanY - 1); y++) {
+ for (int i = 0; i < spanX; i++) {
+ for (int j = 0; j < spanY; j++) {
+ if (occupied[x + i][y + j]) {
+ // small optimization: we can skip to below the row we just found
+ // an occupied cell
+ y += j;
+ continue inner;
+ }
+ }
+ }
+ final int[] cellXY = mTmpCellXY;
+ cellToPoint(x, y, cellXY);
+
+ double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
+ + Math.pow(cellXY[1] - pixelY, 2));
+ if (distance <= bestDistance) {
+ bestDistance = distance;
+ bestXY[0] = x;
+ bestXY[1] = y;
+ }
}
}
+ // re-mark space taken by ignoreView as occupied
+ markCellsAsOccupiedForView(ignoreView);
- // Return null if no suitable location found
+ // Return null if no suitable location found
if (bestDistance < Double.MAX_VALUE) {
return bestXY;
} else {
return null;
}
}
-
+
+ boolean existsEmptyCell() {
+ return findCellForSpan(null, 1, 1);
+ }
+
/**
- * Drop a child at the specified position
+ * Finds the upper-left coordinate of the first rectangle in the grid that can
+ * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
+ * then this method will only return coordinates for rectangles that contain the cell
+ * (intersectX, intersectY)
+ *
+ * @param cellXY The array that will contain the position of a vacant cell if such a cell
+ * can be found.
+ * @param spanX The horizontal span of the cell we want to find.
+ * @param spanY The vertical span of the cell we want to find.
+ *
+ * @return True if a vacant cell of the specified dimension was found, false otherwise.
+ */
+ boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
+ return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null);
+ }
+
+ /**
+ * Like above, but ignores any cells occupied by the item "ignoreView"
+ *
+ * @param cellXY The array that will contain the position of a vacant cell if such a cell
+ * can be found.
+ * @param spanX The horizontal span of the cell we want to find.
+ * @param spanY The vertical span of the cell we want to find.
+ * @param ignoreView The home screen item we should treat as not occupying any space
+ * @return
+ */
+ boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
+ return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, ignoreView);
+ }
+
+ /**
+ * Like above, but if intersectX and intersectY are not -1, then this method will try to
+ * return coordinates for rectangles that contain the cell [intersectX, intersectY]
+ *
+ * @param spanX The horizontal span of the cell we want to find.
+ * @param spanY The vertical span of the cell we want to find.
+ * @param ignoreView The home screen item we should treat as not occupying any space
+ * @param intersectX The X coordinate of the cell that we should try to overlap
+ * @param intersectX The Y coordinate of the cell that we should try to overlap
+ *
+ * @return True if a vacant cell of the specified dimension was found, false otherwise.
+ */
+ boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
+ int intersectX, int intersectY) {
+ return findCellForSpanThatIntersectsIgnoring(
+ cellXY, spanX, spanY, intersectX, intersectY, null);
+ }
+
+ /**
+ * The superset of the above two methods
+ */
+ boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
+ int intersectX, int intersectY, View ignoreView) {
+ // mark space take by ignoreView as available (method checks if ignoreView is null)
+ markCellsAsUnoccupiedForView(ignoreView);
+
+ boolean foundCell = false;
+ while (true) {
+ int startX = 0;
+ if (intersectX >= 0) {
+ startX = Math.max(startX, intersectX - (spanX - 1));
+ }
+ int endX = mCountX - (spanX - 1);
+ if (intersectX >= 0) {
+ endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
+ }
+ int startY = 0;
+ if (intersectY >= 0) {
+ startY = Math.max(startY, intersectY - (spanY - 1));
+ }
+ int endY = mCountY - (spanY - 1);
+ if (intersectY >= 0) {
+ endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
+ }
+
+ for (int x = startX; x < endX; x++) {
+ inner:
+ for (int y = startY; y < endY; y++) {
+ for (int i = 0; i < spanX; i++) {
+ for (int j = 0; j < spanY; j++) {
+ if (mOccupied[x + i][y + j]) {
+ // small optimization: we can skip to below the row we just found
+ // an occupied cell
+ y += j;
+ continue inner;
+ }
+ }
+ }
+ if (cellXY != null) {
+ cellXY[0] = x;
+ cellXY[1] = y;
+ }
+ foundCell = true;
+ break;
+ }
+ }
+ if (intersectX == -1 && intersectY == -1) {
+ break;
+ } else {
+ // if we failed to find anything, try again but without any requirements of
+ // intersecting
+ intersectX = -1;
+ intersectY = -1;
+ continue;
+ }
+ }
+
+ // re-mark space taken by ignoreView as occupied
+ markCellsAsOccupiedForView(ignoreView);
+ return foundCell;
+ }
+
+ /**
+ * Called when drag has left this CellLayout or has been completed (successfully or not)
+ */
+ void onDragExit() {
+ // This can actually be called when we aren't in a drag, e.g. when adding a new
+ // item to this layout via the customize drawer.
+ // Guard against that case.
+ if (mDragging) {
+ mDragging = false;
+
+ // Fade out the drag indicators
+ if (mCrosshairsAnimator != null) {
+ mCrosshairsAnimator.animateOut();
+ }
+ }
+
+ // Invalidate the drag data
+ mDragCell[0] = -1;
+ mDragCell[1] = -1;
+ mDragOutlineAnims[mDragOutlineCurrent].animateOut();
+ mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
+
+ setHover(false);
+ }
+
+ /**
+ * Mark a child as having been dropped.
+ * At the beginning of the drag operation, the child may have been on another
+ * screen, but it is re-parented before this method is called.
*
* @param child The child that is being dropped
- * @param targetXY Destination area to move to
*/
- void onDropChild(View child, int[] targetXY) {
+ void onDropChild(View child) {
if (child != null) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
- lp.cellX = targetXY[0];
- lp.cellY = targetXY[1];
lp.isDragging = false;
lp.dropped = true;
- mDragRect.setEmpty();
+ child.setVisibility(View.VISIBLE);
child.requestLayout();
- invalidate();
- }
- }
-
- void onDropAborted(View child) {
- if (child != null) {
- ((LayoutParams) child.getLayoutParams()).isDragging = false;
- invalidate();
}
- mDragRect.setEmpty();
}
/**
* Start dragging the specified child
- *
+ *
* @param child The child that is being dragged
*/
void onDragChild(View child) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.isDragging = true;
- mDragRect.setEmpty();
+ child.setVisibility(View.GONE);
}
-
+
/**
- * Drag a child over the specified position
- *
- * @param child The child that is being dropped
- * @param cellX The child's new x cell location
- * @param cellY The child's new y cell location
+ * A drag event has begun over this layout.
+ * It may have begun over this layout (in which case onDragChild is called first),
+ * or it may have begun on another layout.
*/
- void onDragOverChild(View child, int cellX, int cellY) {
- int[] cellXY = mCellXY;
- pointToCellRounded(cellX, cellY, cellXY);
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- cellToRect(cellXY[0], cellXY[1], lp.cellHSpan, lp.cellVSpan, mDragRect);
- invalidate();
+ void onDragEnter() {
+ if (!mDragging) {
+ // Fade in the drag indicators
+ if (mCrosshairsAnimator != null) {
+ mCrosshairsAnimator.animateIn();
+ }
+ }
+ mDragging = true;
}
-
+
/**
* Computes a bounding rectangle for a range of cells
- *
+ *
* @param cellX X coordinate of upper left corner expressed as a cell position
* @param cellY Y coordinate of upper left corner expressed as a cell position
- * @param cellHSpan Width in cells
+ * @param cellHSpan Width in cells
* @param cellVSpan Height in cells
- * @param dragRect Rectnagle into which to put the results
+ * @param resultRect Rect into which to put the results
*/
- public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF dragRect) {
- final boolean portrait = mPortrait;
+ public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) {
final int cellWidth = mCellWidth;
final int cellHeight = mCellHeight;
final int widthGap = mWidthGap;
final int heightGap = mHeightGap;
-
- final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
- final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
-
+
+ final int hStartPadding = getLeftPadding();
+ final int vStartPadding = getTopPadding();
+
int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
int x = hStartPadding + cellX * (cellWidth + widthGap);
int y = vStartPadding + cellY * (cellHeight + heightGap);
-
- dragRect.set(x, y, x + width, y + height);
+
+ resultRect.set(x, y, x + width, y + height);
}
-
+
/**
- * Computes the required horizontal and vertical cell spans to always
+ * Computes the required horizontal and vertical cell spans to always
* fit the given rectangle.
- *
+ *
* @param width Width in pixels
* @param height Height in pixels
+ * @param result An array of length 2 in which to store the result (may be null).
*/
- public int[] rectToCell(int width, int height) {
+ public int[] rectToCell(int width, int height, int[] result) {
+ return rectToCell(getResources(), width, height, result);
+ }
+
+ public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
// Always assume we're working with the smallest span to make sure we
// reserve enough space in both orientations.
- final Resources resources = getResources();
int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
int smallerSize = Math.min(actualWidth, actualHeight);
@@ -749,7 +1305,12 @@ public class CellLayout extends ViewGroup {
int spanX = (width + smallerSize) / smallerSize;
int spanY = (height + smallerSize) / smallerSize;
- return new int[] { spanX, spanY };
+ if (result == null) {
+ return new int[] { spanX, spanY };
+ }
+ result[0] = spanX;
+ result[1] = spanY;
+ return result;
}
/**
@@ -758,18 +1319,12 @@ public class CellLayout extends ViewGroup {
* @param vacant Holds the x and y coordinate of the vacant cell
* @param spanX Horizontal cell span.
* @param spanY Vertical cell span.
- *
+ *
* @return True if a vacant cell was found
*/
public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
- final boolean portrait = mPortrait;
- final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
- final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
- final boolean[][] occupied = mOccupied;
-
- findOccupiedCells(xCount, yCount, occupied, null);
- return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
+ return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
}
static boolean findVacantCell(int[] vacant, int spanX, int spanY,
@@ -796,43 +1351,36 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
return false;
}
- boolean[] getOccupiedCells() {
- final boolean portrait = mPortrait;
- final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
- final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
- final boolean[][] occupied = mOccupied;
-
- findOccupiedCells(xCount, yCount, occupied, null);
-
- final boolean[] flat = new boolean[xCount * yCount];
- for (int y = 0; y < yCount; y++) {
- for (int x = 0; x < xCount; x++) {
- flat[y * xCount + x] = occupied[x][y];
+ private void clearOccupiedCells() {
+ for (int x = 0; x < mCountX; x++) {
+ for (int y = 0; y < mCountY; y++) {
+ mOccupied[x][y] = false;
}
}
+ }
- return flat;
+ public void onMove(View view, int newCellX, int newCellY) {
+ LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ markCellsAsUnoccupiedForView(view);
+ markCellsForView(newCellX, newCellY, lp.cellHSpan, lp.cellVSpan, true);
}
- private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied, View ignoreView) {
- for (int x = 0; x < xCount; x++) {
- for (int y = 0; y < yCount; y++) {
- occupied[x][y] = false;
- }
- }
+ private void markCellsAsOccupiedForView(View view) {
+ if (view == null || view.getParent() != this) return;
+ LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
+ }
- int count = getChildCount();
- for (int i = 0; i < count; i++) {
- View child = getChildAt(i);
- if (child instanceof Folder || child.equals(ignoreView)) {
- continue;
- }
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ private void markCellsAsUnoccupiedForView(View view) {
+ if (view == null || view.getParent() != this) return;
+ LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
+ }
- for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
- for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
- occupied[x][y] = true;
- }
+ private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean value) {
+ for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
+ for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
+ mOccupied[x][y] = value;
}
}
}
@@ -852,6 +1400,17 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
return new CellLayout.LayoutParams(p);
}
+ public static class CellLayoutAnimationController extends LayoutAnimationController {
+ public CellLayoutAnimationController(Animation animation, float delay) {
+ super(animation, delay);
+ }
+
+ @Override
+ protected long getDelayForView(View view) {
+ return (int) (Math.random() * 150);
+ }
+ }
+
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
/**
* Horizontal location of the item in the grid.
@@ -876,7 +1435,7 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
*/
@ViewDebug.ExportedProperty
public int cellVSpan;
-
+
/**
* Is this item currently being dragged
*/
@@ -889,8 +1448,18 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
@ViewDebug.ExportedProperty
int y;
- boolean regenerateId;
-
+ /**
+ * The old X coordinate of this item, relative to its current parent.
+ * Used to animate the item into its new position.
+ */
+ int oldX;
+
+ /**
+ * The old Y coordinate of this item, relative to its current parent.
+ * Used to animate the item into its new position.
+ */
+ int oldY;
+
boolean dropped;
public LayoutParams(Context c, AttributeSet attrs) {
@@ -904,7 +1473,15 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
cellHSpan = 1;
cellVSpan = 1;
}
-
+
+ public LayoutParams(LayoutParams source) {
+ super(source);
+ this.cellX = source.cellX;
+ this.cellY = source.cellY;
+ this.cellHSpan = source.cellHSpan;
+ this.cellVSpan = source.cellVSpan;
+ }
+
public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
this.cellX = cellX;
@@ -915,12 +1492,12 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
int hStartPadding, int vStartPadding) {
-
+
final int myCellHSpan = cellHSpan;
final int myCellVSpan = cellVSpan;
final int myCellX = cellX;
final int myCellY = cellY;
-
+
width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
leftMargin - rightMargin;
height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
@@ -929,172 +1506,31 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
}
- }
-
- static final class CellInfo implements ContextMenu.ContextMenuInfo {
- /**
- * See View.AttachInfo.InvalidateInfo for futher explanations about
- * the recycling mechanism. In this case, we recycle the vacant cells
- * instances because up to several hundreds can be instanciated when
- * the user long presses an empty cell.
- */
- static final class VacantCell {
- int cellX;
- int cellY;
- int spanX;
- int spanY;
-
- // We can create up to 523 vacant cells on a 4x4 grid, 100 seems
- // like a reasonable compromise given the size of a VacantCell and
- // the fact that the user is not likely to touch an empty 4x4 grid
- // very often
- private static final int POOL_LIMIT = 100;
- private static final Object sLock = new Object();
-
- private static int sAcquiredCount = 0;
- private static VacantCell sRoot;
-
- private VacantCell next;
-
- static VacantCell acquire() {
- synchronized (sLock) {
- if (sRoot == null) {
- return new VacantCell();
- }
-
- VacantCell info = sRoot;
- sRoot = info.next;
- sAcquiredCount--;
-
- return info;
- }
- }
- void release() {
- synchronized (sLock) {
- if (sAcquiredCount < POOL_LIMIT) {
- sAcquiredCount++;
- next = sRoot;
- sRoot = this;
- }
- }
- }
-
- @Override
- public String toString() {
- return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX +
- ", spanY=" + spanY + "]";
- }
+ public String toString() {
+ return "(" + this.cellX + ", " + this.cellY + ")";
}
+ }
+ // This class stores info for two purposes:
+ // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
+ // its spanX, spanY, and the screen it is on
+ // 2. When long clicking on an empty cell in a CellLayout, we save information about the
+ // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
+ // the CellLayout that was long clicked
+ static final class CellInfo implements ContextMenu.ContextMenuInfo {
View cell;
- int cellX;
- int cellY;
+ int cellX = -1;
+ int cellY = -1;
int spanX;
int spanY;
int screen;
boolean valid;
- final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
- int maxVacantSpanX;
- int maxVacantSpanXSpanY;
- int maxVacantSpanY;
- int maxVacantSpanYSpanX;
- final Rect current = new Rect();
-
- void clearVacantCells() {
- final ArrayList<VacantCell> list = vacantCells;
- final int count = list.size();
-
- for (int i = 0; i < count; i++) list.get(i).release();
-
- list.clear();
- }
-
- void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
- if (cellX < 0 || cellY < 0) {
- maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
- maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
- clearVacantCells();
- return;
- }
-
- final boolean[][] unflattened = new boolean[xCount][yCount];
- for (int y = 0; y < yCount; y++) {
- for (int x = 0; x < xCount; x++) {
- unflattened[x][y] = occupied[y * xCount + x];
- }
- }
- CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
- }
-
- /**
- * This method can be called only once! Calling #findVacantCellsFromOccupied will
- * restore the ability to call this method.
- *
- * Finds the upper-left coordinate of the first rectangle in the grid that can
- * hold a cell of the specified dimensions.
- *
- * @param cellXY The array that will contain the position of a vacant cell if such a cell
- * can be found.
- * @param spanX The horizontal span of the cell we want to find.
- * @param spanY The vertical span of the cell we want to find.
- *
- * @return True if a vacant cell of the specified dimension was found, false otherwise.
- */
- boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
- return findCellForSpan(cellXY, spanX, spanY, true);
- }
-
- boolean findCellForSpan(int[] cellXY, int spanX, int spanY, boolean clear) {
- final ArrayList<VacantCell> list = vacantCells;
- final int count = list.size();
-
- boolean found = false;
-
- if (this.spanX >= spanX && this.spanY >= spanY) {
- cellXY[0] = cellX;
- cellXY[1] = cellY;
- found = true;
- }
-
- // Look for an exact match first
- for (int i = 0; i < count; i++) {
- VacantCell cell = list.get(i);
- if (cell.spanX == spanX && cell.spanY == spanY) {
- cellXY[0] = cell.cellX;
- cellXY[1] = cell.cellY;
- found = true;
- break;
- }
- }
-
- // Look for the first cell large enough
- for (int i = 0; i < count; i++) {
- VacantCell cell = list.get(i);
- if (cell.spanX >= spanX && cell.spanY >= spanY) {
- cellXY[0] = cell.cellX;
- cellXY[1] = cell.cellY;
- found = true;
- break;
- }
- }
-
- if (clear) clearVacantCells();
-
- return found;
- }
-
@Override
public String toString() {
- return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX +
- ", y=" + cellY + "]";
+ return "Cell[view=" + (cell == null ? "null" : cell.getClass())
+ + ", x=" + cellX + ", y=" + cellY + "]";
}
}
-
- public boolean lastDownOnOccupiedCell() {
- return mLastDownOnOccupiedCell;
- }
}
-
-
diff --git a/src/com/android/launcher2/CustomizePagedView.java b/src/com/android/launcher2/CustomizePagedView.java
new file mode 100644
index 000000000..4cce81e97
--- /dev/null
+++ b/src/com/android/launcher2/CustomizePagedView.java
@@ -0,0 +1,795 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.Region.Op;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.ActionMode;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher.R;
+
+public class CustomizePagedView extends PagedView
+ implements View.OnLongClickListener, View.OnClickListener,
+ DragSource, ActionMode.Callback {
+
+ public enum CustomizationType {
+ WidgetCustomization,
+ ShortcutCustomization,
+ WallpaperCustomization,
+ ApplicationCustomization
+ }
+
+ private static final String TAG = "CustomizeWorkspace";
+ private static final boolean DEBUG = false;
+
+ private Launcher mLauncher;
+ private DragController mDragController;
+ private PackageManager mPackageManager;
+
+ private CustomizationType mCustomizationType;
+
+ // The layout used to emulate the workspace in resolve the cell dimensions of a widget
+ private PagedViewCellLayout mWorkspaceWidgetLayout;
+
+ // The mapping between the pages and the widgets that will be laid out on them
+ private ArrayList<ArrayList<AppWidgetProviderInfo>> mWidgetPages;
+
+ // The max dimensions for the ImageView we use for displaying the widget
+ private int mMaxWidgetWidth;
+
+ // The max number of widget cells to take a "page" of widget
+ private int mMaxWidgetsCellHSpan;
+
+ // The raw sources of data for each of the different tabs of the customization page
+ private List<AppWidgetProviderInfo> mWidgetList;
+ private List<ResolveInfo> mShortcutList;
+ private List<ResolveInfo> mWallpaperList;
+ private List<ApplicationInfo> mApps;
+
+ private static final int sMinWidgetCellHSpan = 2;
+ private static final int sMaxWidgetCellHSpan = 4;
+
+ private int mChoiceModeTitleText;
+
+ // The scale factor for widget previews inside the widget drawer
+ private static final float sScaleFactor = 0.75f;
+
+ private final Canvas mCanvas = new Canvas();
+ private final LayoutInflater mInflater;
+
+ public CustomizePagedView(Context context) {
+ this(context, null, 0);
+ }
+
+ public CustomizePagedView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CustomizePagedView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a;
+ a = context.obtainStyledAttributes(attrs, R.styleable.CustomizePagedView,
+ defStyle, 0);
+ mMaxWidgetsCellHSpan = a.getInt(R.styleable.CustomizePagedView_widgetCellCountX, 8);
+ a.recycle();
+ a = context.obtainStyledAttributes(attrs, R.styleable.PagedView, defStyle, 0);
+ mCellCountX = a.getInt(R.styleable.PagedView_cellCountX, 7);
+ mCellCountY = a.getInt(R.styleable.PagedView_cellCountY, 4);
+
+ a.recycle();
+ mCustomizationType = CustomizationType.WidgetCustomization;
+ mWidgetPages = new ArrayList<ArrayList<AppWidgetProviderInfo>>();
+ mWorkspaceWidgetLayout = new PagedViewCellLayout(context);
+ mInflater = LayoutInflater.from(context);
+
+ setVisibility(View.GONE);
+ setSoundEffectsEnabled(false);
+ setupWorkspaceLayout();
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+ mCenterPagesVertically = false;
+ }
+
+ public void setLauncher(Launcher launcher) {
+ Context context = getContext();
+ mLauncher = launcher;
+ mPackageManager = context.getPackageManager();
+ }
+
+ /**
+ * Sets the list of applications that launcher has loaded.
+ */
+ public void setApps(ArrayList<ApplicationInfo> list) {
+ mApps = list;
+ Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
+
+ // Update the widgets/shortcuts to reflect changes in the set of available apps
+ invalidatePageDataAndIconCache();
+ }
+
+ /**
+ * Convenience function to add new items to the set of applications that were previously loaded.
+ * Called by both updateApps() and setApps().
+ */
+ private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
+ // we add it in place, in alphabetical order
+ final int count = list.size();
+ for (int i = 0; i < count; ++i) {
+ final ApplicationInfo info = list.get(i);
+ final int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR);
+ if (index < 0) {
+ mApps.add(-(index + 1), info);
+ }
+ }
+ }
+
+ /**
+ * Adds new applications to the loaded list, and notifies the paged view to update itself.
+ */
+ public void addApps(ArrayList<ApplicationInfo> list) {
+ addAppsWithoutInvalidate(list);
+
+ // Update the widgets/shortcuts to reflect changes in the set of available apps
+ invalidatePageDataAndIconCache();
+ }
+
+ /**
+ * Convenience function to remove items to the set of applications that were previously loaded.
+ * Called by both updateApps() and removeApps().
+ */
+ private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
+ // loop through all the apps and remove apps that have the same component
+ final int length = list.size();
+ for (int i = 0; i < length; ++i) {
+ final ApplicationInfo info = list.get(i);
+ int removeIndex = findAppByComponent(mApps, info);
+ if (removeIndex > -1) {
+ mApps.remove(removeIndex);
+ mPageViewIconCache.removeOutline(info);
+ }
+ }
+ }
+
+ /**
+ * Removes applications from the loaded list, and notifies the paged view to update itself.
+ */
+ public void removeApps(ArrayList<ApplicationInfo> list) {
+ removeAppsWithoutInvalidate(list);
+
+ // Update the widgets/shortcuts to reflect changes in the set of available apps
+ invalidatePageDataAndIconCache();
+ }
+
+ /**
+ * Updates a set of applications from the loaded list, and notifies the paged view to update
+ * itself.
+ */
+ public void updateApps(ArrayList<ApplicationInfo> list) {
+ // We remove and re-add the updated applications list because it's properties may have
+ // changed (ie. the title), and this will ensure that the items will be in their proper
+ // place in the list.
+ removeAppsWithoutInvalidate(list);
+ addAppsWithoutInvalidate(list);
+
+ // Update the widgets/shortcuts to reflect changes in the set of available apps
+ invalidatePageDataAndIconCache();
+ }
+
+ /**
+ * Convenience function to find matching ApplicationInfos for removal.
+ */
+ private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
+ ComponentName removeComponent = item.intent.getComponent();
+ final int length = list.size();
+ for (int i = 0; i < length; ++i) {
+ ApplicationInfo info = list.get(i);
+ if (info.intent.getComponent().equals(removeComponent)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public void update() {
+ // get the list of widgets
+ mWidgetList = AppWidgetManager.getInstance(mLauncher).getInstalledProviders();
+ Collections.sort(mWidgetList, new Comparator<AppWidgetProviderInfo>() {
+ @Override
+ public int compare(AppWidgetProviderInfo object1, AppWidgetProviderInfo object2) {
+ return object1.label.compareTo(object2.label);
+ }
+ });
+
+ Comparator<ResolveInfo> resolveInfoComparator = new Comparator<ResolveInfo>() {
+ @Override
+ public int compare(ResolveInfo object1, ResolveInfo object2) {
+ return object1.loadLabel(mPackageManager).toString().compareTo(
+ object2.loadLabel(mPackageManager).toString());
+ }
+ };
+
+ // get the list of shortcuts
+ Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+ mShortcutList = mPackageManager.queryIntentActivities(shortcutsIntent, 0);
+ Collections.sort(mShortcutList, resolveInfoComparator);
+
+ // get the list of wallpapers
+ Intent wallpapersIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
+ mWallpaperList = mPackageManager.queryIntentActivities(wallpapersIntent, 0);
+ Collections.sort(mWallpaperList, resolveInfoComparator);
+
+ invalidatePageDataAndIconCache();
+ }
+
+ private void invalidatePageDataAndIconCache() {
+ // Reset the icon cache
+ mPageViewIconCache.clear();
+
+ // Refresh all the tabs
+ invalidatePageData();
+ }
+
+ public void setDragController(DragController dragger) {
+ mDragController = dragger;
+ }
+
+ public void setCustomizationFilter(CustomizationType filterType) {
+ mCustomizationType = filterType;
+ setCurrentPage(0);
+ invalidatePageData();
+
+ // End the current choice mode so that we don't carry selections across tabs
+ endChoiceMode();
+ }
+
+ @Override
+ public void onDropCompleted(View target, boolean success) {
+ mLauncher.getWorkspace().onDragStopped();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (!v.isInTouchMode()) {
+ return;
+ }
+
+ // On certain pages, we allow single tap to mark items as selected so that they can be
+ // dropped onto the mini workspaces
+ boolean enterChoiceMode = false;
+ switch (mCustomizationType) {
+ case WidgetCustomization:
+ mChoiceModeTitleText = R.string.cab_widget_selection_text;
+ enterChoiceMode = true;
+ break;
+ case ApplicationCustomization:
+ mChoiceModeTitleText = R.string.cab_app_selection_text;
+ enterChoiceMode = true;
+ break;
+ case ShortcutCustomization:
+ mChoiceModeTitleText = R.string.cab_shortcut_selection_text;
+ enterChoiceMode = true;
+ break;
+ default:
+ break;
+ }
+
+ if (enterChoiceMode) {
+ final ItemInfo itemInfo = (ItemInfo) v.getTag();
+
+ Workspace w = mLauncher.getWorkspace();
+ int currentWorkspaceScreen = mLauncher.getCurrentWorkspaceScreen();
+ final CellLayout cl = (CellLayout)w.getChildAt(currentWorkspaceScreen);
+
+ animateClickFeedback(v, new Runnable() {
+ @Override
+ public void run() {
+ mLauncher.addExternalItemToScreen(itemInfo, cl);
+ }
+ });
+ return;
+ }
+
+ // Otherwise, we just handle the single click here
+ switch (mCustomizationType) {
+ case WallpaperCustomization:
+ // animate some feedback to the long press
+ final View clickView = v;
+ animateClickFeedback(v, new Runnable() {
+ @Override
+ public void run() {
+ // add the shortcut
+ ResolveInfo info = (ResolveInfo) clickView.getTag();
+ Intent createWallpapersIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
+ ComponentName name = new ComponentName(info.activityInfo.packageName,
+ info.activityInfo.name);
+ createWallpapersIntent.setComponent(name);
+ mLauncher.processWallpaper(createWallpapersIntent);
+ }
+ });
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ if (!v.isInTouchMode()) {
+ return false;
+ }
+
+ // End the current choice mode before we start dragging anything
+ if (isChoiceMode(CHOICE_MODE_SINGLE)) {
+ endChoiceMode();
+ }
+
+ PendingAddItemInfo createItemInfo;
+ switch (mCustomizationType) {
+ case WidgetCustomization:
+ // Get the icon as the drag representation
+ final LinearLayout l = (LinearLayout) v;
+ final Drawable icon = ((ImageView) l.findViewById(R.id.widget_preview)).getDrawable();
+ Bitmap b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(),
+ Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(b);
+ icon.draw(c);
+ PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) v.getTag();
+
+ mLauncher.getWorkspace().onDragStartedWithItemMinSize(
+ createWidgetInfo.minWidth, createWidgetInfo.minHeight);
+ mDragController.startDrag(v, b, this, createWidgetInfo, DragController.DRAG_ACTION_COPY,
+ null);
+
+ // Cleanup the icon
+ b.recycle();
+ return true;
+ case ShortcutCustomization:
+ createItemInfo = (PendingAddItemInfo) v.getTag();
+ mDragController.startDrag(
+ v, this, createItemInfo, DragController.DRAG_ACTION_COPY, null);
+ mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1);
+ return true;
+ case ApplicationCustomization:
+ // Pick up the application for dropping
+ ApplicationInfo app = (ApplicationInfo) v.getTag();
+ app = new ApplicationInfo(app);
+
+ mDragController.startDrag(v, this, app, DragController.DRAG_ACTION_COPY);
+ mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Pre-processes the layout of the different widget pages.
+ * @return the number of pages of widgets that we have
+ */
+ private int relayoutWidgets() {
+ if (mWidgetList.isEmpty()) return 0;
+
+ // create a new page for the first set of widgets
+ ArrayList<AppWidgetProviderInfo> newPage = new ArrayList<AppWidgetProviderInfo>();
+ mWidgetPages.clear();
+ mWidgetPages.add(newPage);
+
+ // do this until we have no more widgets to lay out
+ final int maxNumCellsPerRow = mMaxWidgetsCellHSpan;
+ final int widgetCount = mWidgetList.size();
+ int numCellsInRow = 0;
+ for (int i = 0; i < widgetCount; ++i) {
+ final AppWidgetProviderInfo info = mWidgetList.get(i);
+
+ // determine the size of the current widget
+ int cellSpanX = Math.max(sMinWidgetCellHSpan, Math.min(sMaxWidgetCellHSpan,
+ mWorkspaceWidgetLayout.estimateCellHSpan(info.minWidth)));
+
+ // create a new page if necessary
+ if ((numCellsInRow + cellSpanX) > maxNumCellsPerRow) {
+ numCellsInRow = 0;
+ newPage = new ArrayList<AppWidgetProviderInfo>();
+ mWidgetPages.add(newPage);
+ }
+
+ // add the item to the current page
+ newPage.add(info);
+ numCellsInRow += cellSpanX;
+ }
+
+ return mWidgetPages.size();
+ }
+
+ /**
+ * Helper function to draw a drawable to the specified canvas with the specified bounds.
+ */
+ private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int l, int t, int r, int b) {
+ if (bitmap != null) mCanvas.setBitmap(bitmap);
+ mCanvas.save();
+ d.setBounds(l, t, r, b);
+ d.draw(mCanvas);
+ mCanvas.restore();
+ }
+
+ /**
+ * This method will extract the preview image specified by the widget developer (if it exists),
+ * otherwise, it will try to generate a default image preview with the widget's package icon.
+ * @return the drawable will be used and sized in the ImageView to represent the widget
+ */
+ private Drawable getWidgetIcon(AppWidgetProviderInfo info) {
+ PackageManager packageManager = mLauncher.getPackageManager();
+ String packageName = info.provider.getPackageName();
+ Drawable drawable = null;
+ if (info.previewImage != 0) {
+ drawable = packageManager.getDrawable(packageName, info.previewImage, null);
+ if (drawable == null) {
+ Log.w(TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon)
+ + " for provider: " + info.provider);
+ }
+ }
+
+ // If we don't have a preview image, create a default one
+ final int minDim = mWorkspaceWidgetLayout.estimateCellWidth(1);
+ final int maxDim = mWorkspaceWidgetLayout.estimateCellWidth(3);
+ if (drawable == null) {
+ Resources resources = mLauncher.getResources();
+
+ // Create a new bitmap to hold the widget preview
+ int width = (int) (Math.max(minDim, Math.min(maxDim, info.minWidth)) * sScaleFactor);
+ int height = (int) (Math.max(minDim, Math.min(maxDim, info.minHeight)) * sScaleFactor);
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+ final Drawable background = resources.getDrawable(R.drawable.default_widget_preview);
+ renderDrawableToBitmap(background, bitmap, 0, 0, width, height);
+
+ // Draw the icon vertically centered, flush left
+ try {
+ Rect tmpRect = new Rect();
+ Drawable icon = null;
+ if (info.icon > 0) {
+ icon = packageManager.getDrawable(packageName, info.icon, null);
+ }
+ if (icon == null) {
+ icon = resources.getDrawable(R.drawable.ic_launcher_application);
+ }
+ background.getPadding(tmpRect);
+
+ final int iconSize = minDim / 2;
+ final int offset = iconSize / 4;
+ final int offsetIconSize = offset + iconSize;
+ renderDrawableToBitmap(icon, null, offset, offset, offsetIconSize, offsetIconSize);
+ } catch (Resources.NotFoundException e) {
+ // if we can't find the icon, then just don't draw it
+ }
+
+ drawable = new FastBitmapDrawable(bitmap);
+ } else {
+ // Scale down the preview if necessary
+ final float aspect = (float) info.minWidth / info.minHeight;
+ final int boundedWidth = (int) Math.max(minDim, Math.min(maxDim, info.minWidth));
+ final int boundedHeight = (int) Math.max(minDim, Math.min(maxDim, info.minHeight));
+ final int scaledWidth = (int) (boundedWidth * sScaleFactor);
+ final int scaledHeight = (int) (boundedHeight * sScaleFactor);
+ int width;
+ int height;
+ if (scaledWidth > scaledHeight) {
+ width = scaledWidth;
+ height = (int) (width / aspect);
+ } else {
+ height = scaledHeight;
+ width = (int) (height * aspect);
+ }
+
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+ renderDrawableToBitmap(drawable, bitmap, 0, 0, width, height);
+
+ drawable = new FastBitmapDrawable(bitmap);
+ }
+ drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+ return drawable;
+ }
+
+ private void setupPage(PagedViewCellLayout layout) {
+ layout.setCellCount(mCellCountX, mCellCountY);
+ layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, mPageLayoutPaddingRight,
+ mPageLayoutPaddingBottom);
+ layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap);
+ }
+
+ private void setupWorkspaceLayout() {
+ mWorkspaceWidgetLayout.setCellCount(mCellCountX, mCellCountY);
+ mWorkspaceWidgetLayout.setPadding(20, 10, 20, 0);
+
+ mMaxWidgetWidth = mWorkspaceWidgetLayout.estimateCellWidth(sMaxWidgetCellHSpan);
+ }
+
+ private void syncWidgetPages() {
+ if (mWidgetList == null) return;
+
+ // we need to repopulate with the LinearLayout layout for the widget pages
+ removeAllViews();
+ int numPages = relayoutWidgets();
+ for (int i = 0; i < numPages; ++i) {
+ LinearLayout layout = new PagedViewWidgetLayout(getContext());
+ layout.setGravity(Gravity.CENTER_HORIZONTAL);
+ layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
+ mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
+
+ // Temporary change to prevent the last page from being too small (and items bleeding
+ // onto it). We can remove this once we properly fix the fading algorithm
+ if (i < numPages - 1) {
+ addView(layout, new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.MATCH_PARENT));
+ } else {
+ addView(layout, new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.MATCH_PARENT));
+ }
+ }
+ }
+
+ private void syncWidgetPageItems(int page) {
+ // ensure that we have the right number of items on the pages
+ LinearLayout layout = (LinearLayout) getChildAt(page);
+ final ArrayList<AppWidgetProviderInfo> list = mWidgetPages.get(page);
+ final int count = list.size();
+ layout.removeAllViews();
+ for (int i = 0; i < count; ++i) {
+ final AppWidgetProviderInfo info = (AppWidgetProviderInfo) list.get(i);
+ final PendingAddWidgetInfo createItemInfo = new PendingAddWidgetInfo(info, null, null);
+
+ LinearLayout l = (LinearLayout) mInflater.inflate(
+ R.layout.customize_paged_view_widget, layout, false);
+ l.setTag(createItemInfo);
+ l.setOnClickListener(this);
+ l.setOnLongClickListener(this);
+
+ final Drawable icon = getWidgetIcon(info);
+
+ int[] spans = CellLayout.rectToCell(getResources(), info.minWidth, info.minHeight, null);
+ final int hSpan = spans[0];
+ final int vSpan = spans[1];
+
+ ImageView image = (ImageView) l.findViewById(R.id.widget_preview);
+ image.setMaxWidth(mMaxWidgetWidth);
+ image.setImageDrawable(icon);
+ TextView name = (TextView) l.findViewById(R.id.widget_name);
+ name.setText(info.label);
+ TextView dims = (TextView) l.findViewById(R.id.widget_dims);
+ dims.setText(mContext.getString(R.string.widget_dims_format, hSpan, vSpan));
+
+ layout.addView(l);
+ }
+ }
+
+ private void syncListPages(List<ResolveInfo> list) {
+ // we need to repopulate with PagedViewCellLayouts
+ removeAllViews();
+
+ // ensure that we have the right number of pages
+ int numPages = (int) Math.ceil((float) list.size() / (mCellCountX * mCellCountY));
+ for (int i = 0; i < numPages; ++i) {
+ PagedViewCellLayout layout = new PagedViewCellLayout(getContext());
+ setupPage(layout);
+ addView(layout);
+ }
+ }
+
+ private void syncListPageItems(int page, List<ResolveInfo> list) {
+ // ensure that we have the right number of items on the pages
+ int numCells = mCellCountX * mCellCountY;
+ int startIndex = page * numCells;
+ int endIndex = Math.min(startIndex + numCells, list.size());
+ PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page);
+ // TODO: we can optimize by just re-applying to existing views
+ layout.removeAllViews();
+ for (int i = startIndex; i < endIndex; ++i) {
+ ResolveInfo info = list.get(i);
+ PendingAddItemInfo createItemInfo = new PendingAddItemInfo();
+
+ PagedViewIcon icon = (PagedViewIcon) mInflater.inflate(
+ R.layout.customize_paged_view_item, layout, false);
+ icon.applyFromResolveInfo(info, mPackageManager, mPageViewIconCache,
+ ((LauncherApplication)mLauncher.getApplication()).getIconCache());
+ switch (mCustomizationType) {
+ case WallpaperCustomization:
+ icon.setOnClickListener(this);
+ break;
+ case ShortcutCustomization:
+ createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
+ info.activityInfo.name);
+ icon.setTag(createItemInfo);
+ icon.setOnClickListener(this);
+ icon.setOnLongClickListener(this);
+ break;
+ default:
+ break;
+ }
+
+ final int index = i - startIndex;
+ final int x = index % mCellCountX;
+ final int y = index / mCellCountX;
+ setupPage(layout);
+ layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1));
+ }
+ }
+
+ private void syncAppPages() {
+ if (mApps == null) return;
+
+ // We need to repopulate with PagedViewCellLayouts
+ removeAllViews();
+
+ // Ensure that we have the right number of pages
+ int numPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
+ for (int i = 0; i < numPages; ++i) {
+ PagedViewCellLayout layout = new PagedViewCellLayout(getContext());
+ setupPage(layout);
+ addView(layout);
+ }
+ }
+
+ private void syncAppPageItems(int page) {
+ if (mApps == null) return;
+
+ // ensure that we have the right number of items on the pages
+ int numCells = mCellCountX * mCellCountY;
+ int startIndex = page * numCells;
+ int endIndex = Math.min(startIndex + numCells, mApps.size());
+ PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page);
+ // TODO: we can optimize by just re-applying to existing views
+ layout.removeAllViews();
+ for (int i = startIndex; i < endIndex; ++i) {
+ final ApplicationInfo info = mApps.get(i);
+ PagedViewIcon icon = (PagedViewIcon) mInflater.inflate(
+ R.layout.all_apps_paged_view_application, layout, false);
+ icon.applyFromApplicationInfo(info, mPageViewIconCache, true);
+ icon.setOnClickListener(this);
+ icon.setOnLongClickListener(this);
+
+ final int index = i - startIndex;
+ final int x = index % mCellCountX;
+ final int y = index / mCellCountX;
+ setupPage(layout);
+ layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1));
+ }
+ }
+
+ @Override
+ public void syncPages() {
+ boolean centerPagedViewCellLayouts = false;
+ switch (mCustomizationType) {
+ case WidgetCustomization:
+ syncWidgetPages();
+ break;
+ case ShortcutCustomization:
+ syncListPages(mShortcutList);
+ centerPagedViewCellLayouts = true;
+ break;
+ case WallpaperCustomization:
+ syncListPages(mWallpaperList);
+ centerPagedViewCellLayouts = true;
+ break;
+ case ApplicationCustomization:
+ syncAppPages();
+ centerPagedViewCellLayouts = false;
+ break;
+ default:
+ removeAllViews();
+ setCurrentPage(0);
+ break;
+ }
+
+ // only try and center the page if there is one page
+ final int childCount = getChildCount();
+ if (centerPagedViewCellLayouts) {
+ if (childCount == 1) {
+ PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(0);
+ layout.enableCenteredContent(true);
+ } else {
+ for (int i = 0; i < childCount; ++i) {
+ PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i);
+ layout.enableCenteredContent(false);
+ }
+ }
+ }
+
+ // bound the current page
+ setCurrentPage(Math.max(0, Math.min(childCount - 1, getCurrentPage())));
+ }
+
+ @Override
+ public void syncPageItems(int page) {
+ switch (mCustomizationType) {
+ case WidgetCustomization:
+ syncWidgetPageItems(page);
+ break;
+ case ShortcutCustomization:
+ syncListPageItems(page, mShortcutList);
+ break;
+ case WallpaperCustomization:
+ syncListPageItems(page, mWallpaperList);
+ break;
+ case ApplicationCustomization:
+ syncAppPageItems(page);
+ break;
+ }
+ }
+
+ @Override
+ protected int getAssociatedLowerPageBound(int page) {
+ return 0;
+ }
+ @Override
+ protected int getAssociatedUpperPageBound(int page) {
+ return getChildCount();
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ mode.setTitle(mChoiceModeTitleText);
+ return true;
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ return true;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ endChoiceMode();
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ return false;
+ }
+}
diff --git a/src/com/android/launcher2/DeferredHandler.java b/src/com/android/launcher2/DeferredHandler.java
index 7801642d2..0323c7f4d 100644
--- a/src/com/android/launcher2/DeferredHandler.java
+++ b/src/com/android/launcher2/DeferredHandler.java
@@ -16,13 +16,12 @@
package com.android.launcher2;
+import java.util.LinkedList;
+
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
-import android.util.Log;
-
-import java.util.LinkedList;
/**
* Queue of things to run on a looper thread. Items posted with {@link #post} will not
diff --git a/src/com/android/launcher2/DeleteZone.java b/src/com/android/launcher2/DeleteZone.java
index 4e8b204fb..4acde1c50 100644
--- a/src/com/android/launcher2/DeleteZone.java
+++ b/src/com/android/launcher2/DeleteZone.java
@@ -16,24 +16,24 @@
package com.android.launcher2;
-import android.widget.ImageView;
+import com.android.launcher.R;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.TransitionDrawable;
import android.util.AttributeSet;
import android.view.View;
-import android.view.animation.TranslateAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
-import android.graphics.RectF;
-import android.graphics.drawable.TransitionDrawable;
-
-import com.android.launcher.R;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.TranslateAnimation;
+import android.widget.ImageView;
public class DeleteZone extends ImageView implements DropTarget, DragController.DragListener {
private static final int ORIENTATION_HORIZONTAL = 1;
@@ -45,6 +45,13 @@ public class DeleteZone extends ImageView implements DropTarget, DragController.
private Launcher mLauncher;
private boolean mTrashMode;
+ /**
+ * If true, this View responsible for managing its own visibility, and that of its handle.
+ * This is generally the case, but it will be set to false when this is part of the
+ * Contextual Action Bar.
+ */
+ private boolean mManageVisibility = true;
+
private AnimationSet mInAnimation;
private AnimationSet mOutAnimation;
private Animation mHandleInAnimation;
@@ -53,11 +60,14 @@ public class DeleteZone extends ImageView implements DropTarget, DragController.
private int mOrientation;
private DragController mDragController;
- private final RectF mRegion = new RectF();
+ private final RectF mRegionF = new RectF();
+ private final Rect mRegion = new Rect();
private TransitionDrawable mTransition;
- private View mHandle;
private final Paint mTrashPaint = new Paint();
+ /** The View that this view will replace. */
+ private View mHandle = null;
+
public DeleteZone(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -71,6 +81,7 @@ public class DeleteZone extends ImageView implements DropTarget, DragController.
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DeleteZone, defStyle, 0);
mOrientation = a.getInt(R.styleable.DeleteZone_direction, ORIENTATION_HORIZONTAL);
a.recycle();
+
}
@Override
@@ -83,31 +94,30 @@ public class DeleteZone extends ImageView implements DropTarget, DragController.
DragView dragView, Object dragInfo) {
return true;
}
-
- public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset,
- DragView dragView, Object dragInfo, Rect recycle) {
- return null;
- }
public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
DragView dragView, Object dragInfo) {
final ItemInfo item = (ItemInfo) dragInfo;
+ // On x-large screens, you can uninstall an app by dragging from all apps
+ if (item instanceof ApplicationInfo && LauncherApplication.isScreenXLarge()) {
+ mLauncher.startApplicationUninstallActivity((ApplicationInfo) item);
+ }
+
if (item.container == -1) return;
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
if (item instanceof LauncherAppWidgetInfo) {
mLauncher.removeAppWidget((LauncherAppWidgetInfo) item);
}
- } else {
- if (source instanceof UserFolder) {
- final UserFolder userFolder = (UserFolder) source;
- final UserFolderInfo userFolderInfo = (UserFolderInfo) userFolder.getInfo();
- // Item must be a ShortcutInfo otherwise it couldn't have been in the folder
- // in the first place.
- userFolderInfo.remove((ShortcutInfo)item);
- }
+ } else if (source instanceof UserFolder) {
+ final UserFolder userFolder = (UserFolder) source;
+ final UserFolderInfo userFolderInfo = (UserFolderInfo) userFolder.getInfo();
+ // Item must be a ShortcutInfo otherwise it couldn't have been in the folder
+ // in the first place.
+ userFolderInfo.remove((ShortcutInfo)item);
}
+
if (item instanceof UserFolderInfo) {
final UserFolderInfo userFolderInfo = (UserFolderInfo)item;
LauncherModel.deleteUserFolderContentsFromDatabase(mLauncher, userFolderInfo);
@@ -126,6 +136,7 @@ public class DeleteZone extends ImageView implements DropTarget, DragController.
}.start();
}
}
+
LauncherModel.deleteItemFromDatabase(mLauncher, item);
}
@@ -149,16 +160,27 @@ public class DeleteZone extends ImageView implements DropTarget, DragController.
final ItemInfo item = (ItemInfo) info;
if (item != null) {
mTrashMode = true;
- createAnimations();
- final int[] location = mLocation;
- getLocationOnScreen(location);
- mRegion.set(location[0], location[1], location[0] + mRight - mLeft,
- location[1] + mBottom - mTop);
- mDragController.setDeleteRegion(mRegion);
+ getHitRect(mRegion);
+ mRegionF.set(mRegion);
+
+ if (LauncherApplication.isScreenXLarge()) {
+ // This region will be a "dead zone" for scrolling; make it extend to the edge of
+ // the screen so users don't accidentally trigger a scroll while deleting items
+ mRegionF.top = mLauncher.getWorkspace().getTop();
+ mRegionF.right = mLauncher.getWorkspace().getRight();
+ }
+
+ mDragController.setDeleteRegion(mRegionF);
+
+ // Make sure the icon is set to the default drawable, not the hover drawable
mTransition.resetTransition();
- startAnimation(mInAnimation);
- mHandle.startAnimation(mHandleOutAnimation);
- setVisibility(VISIBLE);
+
+ if (mManageVisibility) {
+ createAnimations();
+ startAnimation(mInAnimation);
+ mHandle.startAnimation(mHandleOutAnimation);
+ setVisibility(VISIBLE);
+ }
}
}
@@ -166,9 +188,33 @@ public class DeleteZone extends ImageView implements DropTarget, DragController.
if (mTrashMode) {
mTrashMode = false;
mDragController.setDeleteRegion(null);
- startAnimation(mOutAnimation);
- mHandle.startAnimation(mHandleInAnimation);
- setVisibility(GONE);
+
+ if (mOutAnimation != null) startAnimation(mOutAnimation);
+ if (mHandleInAnimation != null) mHandle.startAnimation(mHandleInAnimation);
+
+ if (mManageVisibility) {
+ setVisibility(GONE);
+ }
+ }
+ }
+
+ public boolean isDropEnabled() {
+ return true;
+ }
+
+ @Override
+ public void getHitRect(Rect outRect) {
+ super.getHitRect(outRect);
+ if (LauncherApplication.isScreenXLarge()) {
+ // TODO: This is a temporary hack. mManageVisiblity = false when you're in CAB mode.
+ // In that case, this icon is more tightly spaced next to the delete icon so we want
+ // it to have a smaller drag region. When the new drag&drop system comes in, we'll
+ // dispatch the drag/drop by calculating what target you're overlapping
+ final int dragPadding = mManageVisibility ? 50 : 10;
+ outRect.top -= dragPadding;
+ outRect.left -= dragPadding;
+ outRect.bottom += dragPadding;
+ outRect.right += dragPadding;
}
}
@@ -228,6 +274,10 @@ public class DeleteZone extends ImageView implements DropTarget, DragController.
mHandle = view;
}
+ void setManageVisibility(boolean value) {
+ mManageVisibility = value;
+ }
+
private static class FastTranslateAnimation extends TranslateAnimation {
public FastTranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,
int fromYType, float fromYValue, int toYType, float toYValue) {
@@ -261,4 +311,10 @@ public class DeleteZone extends ImageView implements DropTarget, DragController.
return false;
}
}
+
+ @Override
+ public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo) {
+ return null;
+ }
}
diff --git a/src/com/android/launcher2/Dimmable.java b/src/com/android/launcher2/Dimmable.java
new file mode 100644
index 000000000..df43b3c8e
--- /dev/null
+++ b/src/com/android/launcher2/Dimmable.java
@@ -0,0 +1,6 @@
+package com.android.launcher2;
+
+public interface Dimmable {
+ public void setDimmableProgress(float progress);
+ public float getDimmableProgress();
+}
diff --git a/src/com/android/launcher2/DimmableAppWidgetHostView.java b/src/com/android/launcher2/DimmableAppWidgetHostView.java
new file mode 100644
index 000000000..1f512a3f9
--- /dev/null
+++ b/src/com/android/launcher2/DimmableAppWidgetHostView.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import com.android.launcher.R;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.view.View;
+
+public class DimmableAppWidgetHostView extends LauncherAppWidgetHostView implements Dimmable {
+ public DimmableAppWidgetHostView(Context context) {
+ super(context);
+ mPaint.setFilterBitmap(true);
+ }
+
+ private final Paint mPaint = new Paint();
+ private Bitmap mDimmedView;
+ private Canvas mDimmedViewCanvas;
+ private boolean isDimmedViewUpdatePass;
+ private float mDimmableProgress;
+
+ private void setChildAlpha(float alpha) {
+ if (getChildCount() > 0) {
+ final View child = getChildAt(0);
+ if (child.getAlpha() != alpha) {
+ getChildAt(0).setAlpha(alpha);
+ }
+ }
+ }
+
+ private void updateChildAlpha() {
+ // hacky, but sometimes widgets get their alpha set back to 1.0f, so we call
+ // this to force them back
+ setChildAlpha(getAlpha());
+ }
+
+ //@Override
+ public boolean onSetAlpha(int alpha) {
+ super.onSetAlpha(alpha);
+ return true;
+ }
+
+ public void setDimmableProgress(float progress) {
+ mDimmableProgress = progress;
+ }
+
+ public float getDimmableProgress() {
+ return mDimmableProgress;
+ }
+
+ private void updateDimmedView() {
+ if (mDimmedView == null) {
+ mDimmedView = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
+ Bitmap.Config.ARGB_8888);
+ mDimmedViewCanvas = new Canvas(mDimmedView);
+ }
+ mDimmedViewCanvas.drawColor(0x00000000);
+ mDimmedViewCanvas.concat(getMatrix());
+ isDimmedViewUpdatePass = true;
+ draw(mDimmedViewCanvas);
+ // make the bitmap look "dimmed"
+ int dimmedColor = getContext().getResources().getColor(R.color.dimmed_view_color);
+ mDimmedViewCanvas.drawColor(dimmedColor, PorterDuff.Mode.SRC_IN);
+ isDimmedViewUpdatePass = false;
+ invalidate();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (mDimmedView == null && mDimmableProgress > 0.0f) {
+ updateDimmedView();
+ }
+ }
+
+ @Override
+ public void dispatchDraw(Canvas canvas) {
+ if (isDimmedViewUpdatePass) {
+ final float alpha = getAlpha();
+ canvas.save();
+ setAlpha(1.0f);
+ super.dispatchDraw(canvas);
+ canvas.restore();
+ setAlpha(alpha);
+ } else {
+ if (mDimmedView != null && mDimmableProgress > 0) {
+ // draw the dimmed version of this widget
+ mPaint.setAlpha((int) (mDimmableProgress * 255));
+ canvas.drawBitmap(mDimmedView, 0, 0, mPaint);
+ }
+
+ updateChildAlpha();
+ super.dispatchDraw(canvas);
+ }
+ }
+}
diff --git a/src/com/android/launcher2/DimmableBubbleTextView.java b/src/com/android/launcher2/DimmableBubbleTextView.java
new file mode 100644
index 000000000..cb3b8efd6
--- /dev/null
+++ b/src/com/android/launcher2/DimmableBubbleTextView.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import com.android.launcher.R;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.util.AttributeSet;
+
+public class DimmableBubbleTextView extends BubbleTextView implements Dimmable {
+ private Paint mDimmedPaint = new Paint();
+ private int mAlpha;
+ private Bitmap mDimmedView;
+ private Canvas mDimmedViewCanvas;
+ private boolean isDimmedViewUpdatePass;
+ private float mDimmableProgress;
+
+ public DimmableBubbleTextView(Context context) {
+ super(context);
+ mDimmedPaint.setFilterBitmap(true);
+ }
+
+ public DimmableBubbleTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mDimmedPaint.setFilterBitmap(true);
+ }
+
+ public DimmableBubbleTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mDimmedPaint.setFilterBitmap(true);
+ }
+
+ public void setDimmableProgress(float progress) {
+ mDimmableProgress = progress;
+ }
+
+ public float getDimmableProgress() {
+ return mDimmableProgress;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (mDimmedView == null) {
+ isDimmedViewUpdatePass = true;
+ mDimmedView = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
+ Bitmap.Config.ARGB_8888);
+ mDimmedViewCanvas = new Canvas(mDimmedView);
+ mDimmedViewCanvas.concat(getMatrix());
+
+ draw(mDimmedViewCanvas);
+
+ // MAKE THE DIMMED VERSION
+ int dimmedColor = getContext().getResources().getColor(R.color.dimmed_view_color);
+ mDimmedViewCanvas.drawColor(dimmedColor, PorterDuff.Mode.SRC_IN);
+
+ isDimmedViewUpdatePass = false;
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (isDimmedViewUpdatePass) {
+ canvas.save();
+ final float alpha = getAlpha();
+ super.setAlpha(1.0f);
+ super.onDraw(canvas);
+ super.setAlpha(alpha);
+ canvas.restore();
+ } else {
+ super.onDraw(canvas);
+ }
+
+ if (mDimmedView != null && mDimmableProgress > 0) {
+ mDimmedPaint.setAlpha((int) (mDimmableProgress * 255));
+ canvas.drawBitmap(mDimmedView, mScrollX, mScrollY, mDimmedPaint);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java
index b453061df..3c956f07f 100644
--- a/src/com/android/launcher2/DragController.java
+++ b/src/com/android/launcher2/DragController.java
@@ -16,25 +16,27 @@
package com.android.launcher2;
+import com.android.launcher.R;
+
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.os.IBinder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Vibrator;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.view.View;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import java.util.ArrayList;
-import com.android.launcher.R;
-
/**
* Class for initiating a drag within a view or across multiple views.
*/
@@ -56,8 +58,8 @@ public class DragController {
private static final int SCROLL_OUTSIDE_ZONE = 0;
private static final int SCROLL_WAITING_IN_ZONE = 1;
- private static final int SCROLL_LEFT = 0;
- private static final int SCROLL_RIGHT = 1;
+ static final int SCROLL_LEFT = 0;
+ static final int SCROLL_RIGHT = 1;
private Context mContext;
private Handler mHandler;
@@ -105,7 +107,7 @@ public class DragController {
/** Who can receive drop events */
private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>();
- private DragListener mListener;
+ private ArrayList<DragListener> mListeners = new ArrayList<DragListener>();
/** The window token used as the parent for the DragView. */
private IBinder mWindowToken;
@@ -124,6 +126,9 @@ public class DragController {
private InputMethodManager mInputMethodManager;
+ private int mLastTouch[] = new int[2];
+ private int mDistanceSinceScroll = 0;
+
/**
* Interface to receive notifications when a drag starts or stops
*/
@@ -140,7 +145,7 @@ public class DragController {
void onDragStart(DragSource source, Object info, int dragAction);
/**
- * The drag has eneded
+ * The drag has ended
*/
void onDragEnd();
}
@@ -156,9 +161,13 @@ public class DragController {
mScrollZone = context.getResources().getDimensionPixelSize(R.dimen.scroll_zone);
}
+ public boolean dragging() {
+ return mDragging;
+ }
+
/**
* Starts a drag.
- *
+ *
* @param v The view that is being dragged
* @param source An object representing where the drag originated
* @param dragInfo The data associated with the object that is being dragged
@@ -166,6 +175,22 @@ public class DragController {
* {@link #DRAG_ACTION_COPY}
*/
public void startDrag(View v, DragSource source, Object dragInfo, int dragAction) {
+ startDrag(v, source, dragInfo, dragAction, null);
+ }
+
+ /**
+ * Starts a drag.
+ *
+ * @param v The view that is being dragged
+ * @param source An object representing where the drag originated
+ * @param dragInfo The data associated with the object that is being dragged
+ * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
+ * {@link #DRAG_ACTION_COPY}
+ * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
+ * Makes dragging feel more precise, e.g. you can clip out a transparent border
+ */
+ public void startDrag(View v, DragSource source, Object dragInfo, int dragAction,
+ Rect dragRegion) {
mOriginator = v;
Bitmap b = getViewBitmap(v);
@@ -181,7 +206,7 @@ public class DragController {
int screenY = loc[1];
startDrag(b, screenX, screenY, 0, 0, b.getWidth(), b.getHeight(),
- source, dragInfo, dragAction);
+ source, dragInfo, dragAction, dragRegion);
b.recycle();
@@ -192,7 +217,36 @@ public class DragController {
/**
* Starts a drag.
- *
+ *
+ * @param v The view that is being dragged
+ * @param bmp The bitmap that represents the view being dragged
+ * @param source An object representing where the drag originated
+ * @param dragInfo The data associated with the object that is being dragged
+ * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
+ * {@link #DRAG_ACTION_COPY}
+ * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
+ * Makes dragging feel more precise, e.g. you can clip out a transparent border
+ */
+ public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, int dragAction,
+ Rect dragRegion) {
+ mOriginator = v;
+
+ int[] loc = mCoordinatesTemp;
+ v.getLocationOnScreen(loc);
+ int screenX = loc[0];
+ int screenY = loc[1];
+
+ startDrag(bmp, screenX, screenY, 0, 0, bmp.getWidth(), bmp.getHeight(),
+ source, dragInfo, dragAction, dragRegion);
+
+ if (dragAction == DRAG_ACTION_MOVE) {
+ v.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Starts a drag.
+ *
* @param b The bitmap to display as the drag image. It will be re-scaled to the
* enlarged size.
* @param screenX The x position on screen of the left-top of the bitmap.
@@ -209,6 +263,31 @@ public class DragController {
public void startDrag(Bitmap b, int screenX, int screenY,
int textureLeft, int textureTop, int textureWidth, int textureHeight,
DragSource source, Object dragInfo, int dragAction) {
+ startDrag(b, screenX, screenY, textureLeft, textureTop, textureWidth, textureHeight,
+ source, dragInfo, dragAction, null);
+ }
+
+ /**
+ * Starts a drag.
+ *
+ * @param b The bitmap to display as the drag image. It will be re-scaled to the
+ * enlarged size.
+ * @param screenX The x position on screen of the left-top of the bitmap.
+ * @param screenY The y position on screen of the left-top of the bitmap.
+ * @param textureLeft The left edge of the region inside b to use.
+ * @param textureTop The top edge of the region inside b to use.
+ * @param textureWidth The width of the region inside b to use.
+ * @param textureHeight The height of the region inside b to use.
+ * @param source An object representing where the drag originated
+ * @param dragInfo The data associated with the object that is being dragged
+ * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
+ * {@link #DRAG_ACTION_COPY}
+ * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
+ * Makes dragging feel more precise, e.g. you can clip out a transparent border
+ */
+ public void startDrag(Bitmap b, int screenX, int screenY,
+ int textureLeft, int textureTop, int textureWidth, int textureHeight,
+ DragSource source, Object dragInfo, int dragAction, Rect dragRegion) {
if (PROFILE_DRAWING_DURING_DRAG) {
android.os.Debug.startMethodTracing("Launcher");
}
@@ -220,15 +299,17 @@ public class DragController {
}
mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
- if (mListener != null) {
- mListener.onDragStart(source, dragInfo, dragAction);
+ for (DragListener listener : mListeners) {
+ listener.onDragStart(source, dragInfo, dragAction);
}
int registrationX = ((int)mMotionDownX) - screenX;
int registrationY = ((int)mMotionDownY) - screenY;
- mTouchOffsetX = mMotionDownX - screenX;
- mTouchOffsetY = mMotionDownY - screenY;
+ final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
+ final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
+ mTouchOffsetX = mMotionDownX - screenX - dragRegionLeft;
+ mTouchOffsetY = mMotionDownY - screenY - dragRegionTop;
mDragging = true;
mDragSource = source;
@@ -238,7 +319,15 @@ public class DragController {
DragView dragView = mDragView = new DragView(mContext, b, registrationX, registrationY,
textureLeft, textureTop, textureWidth, textureHeight);
+
+ if (dragRegion != null) {
+ dragView.setDragRegion(dragRegionLeft, dragRegion.top,
+ dragRegion.right - dragRegionLeft, dragRegion.bottom - dragRegionTop);
+ }
+
dragView.show(mWindowToken, (int)mMotionDownX, (int)mMotionDownY);
+
+ handleMoveEvent((int) mMotionDownX, (int) mMotionDownY);
}
/**
@@ -304,8 +393,8 @@ public class DragController {
if (mOriginator != null) {
mOriginator.setVisibility(View.VISIBLE);
}
- if (mListener != null) {
- mListener.onDragEnd();
+ for (DragListener listener : mListeners) {
+ listener.onDragEnd();
}
if (mDragView != null) {
mDragView.remove();
@@ -365,12 +454,80 @@ public class DragController {
return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
}
+ private void handleMoveEvent(int x, int y) {
+ mDragView.move(x, y);
+
+ // Drop on someone?
+ final int[] coordinates = mCoordinatesTemp;
+ DropTarget dropTarget = findDropTarget(x, y, coordinates);
+ if (dropTarget != null) {
+ DropTarget delegate = dropTarget.getDropTargetDelegate(
+ mDragSource, coordinates[0], coordinates[1],
+ (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
+ if (delegate != null) {
+ dropTarget = delegate;
+ }
+
+ if (mLastDropTarget != dropTarget) {
+ if (mLastDropTarget != null) {
+ mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
+ (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
+ }
+ dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1],
+ (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
+ }
+ dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1],
+ (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
+ } else {
+ if (mLastDropTarget != null) {
+ mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
+ (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
+ }
+ }
+ mLastDropTarget = dropTarget;
+
+ // Scroll, maybe, but not if we're in the delete region.
+ boolean inDeleteRegion = false;
+ if (mDeleteRegion != null) {
+ inDeleteRegion = mDeleteRegion.contains(x, y);
+ }
+
+ // After a scroll, the touch point will still be in the scroll region.
+ // Rather than scrolling immediately, require a bit of twiddling to scroll again
+ final int slop = ViewConfiguration.get(mContext).getScaledWindowTouchSlop();
+ mDistanceSinceScroll +=
+ Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
+ mLastTouch[0] = x;
+ mLastTouch[1] = y;
+
+ if (!inDeleteRegion && x < mScrollZone) {
+ if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
+ mScrollState = SCROLL_WAITING_IN_ZONE;
+ mScrollRunnable.setDirection(SCROLL_LEFT);
+ mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
+ mDragScroller.onEnterScrollArea(SCROLL_LEFT);
+ }
+ } else if (!inDeleteRegion && x > mScrollView.getWidth() - mScrollZone) {
+ if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
+ mScrollState = SCROLL_WAITING_IN_ZONE;
+ mScrollRunnable.setDirection(SCROLL_RIGHT);
+ mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
+ mDragScroller.onEnterScrollArea(SCROLL_RIGHT);
+ }
+ } else {
+ if (mScrollState == SCROLL_WAITING_IN_ZONE) {
+ mScrollState = SCROLL_OUTSIDE_ZONE;
+ mScrollRunnable.setDirection(SCROLL_RIGHT);
+ mHandler.removeCallbacks(mScrollRunnable);
+ mDragScroller.onExitScrollArea();
+ }
+ }
+ }
+
/**
* Call this from a drag source view.
*/
public boolean onTouchEvent(MotionEvent ev) {
- View scrollView = mScrollView;
-
if (!mDragging) {
return false;
}
@@ -385,69 +542,15 @@ public class DragController {
mMotionDownX = screenX;
mMotionDownY = screenY;
- if ((screenX < mScrollZone) || (screenX > scrollView.getWidth() - mScrollZone)) {
+ if ((screenX < mScrollZone) || (screenX > mScrollView.getWidth() - mScrollZone)) {
mScrollState = SCROLL_WAITING_IN_ZONE;
mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
} else {
mScrollState = SCROLL_OUTSIDE_ZONE;
}
-
break;
case MotionEvent.ACTION_MOVE:
- // Update the drag view. Don't use the clamped pos here so the dragging looks
- // like it goes off screen a little, intead of bumping up against the edge.
- mDragView.move((int)ev.getRawX(), (int)ev.getRawY());
-
- // Drop on someone?
- final int[] coordinates = mCoordinatesTemp;
- DropTarget dropTarget = findDropTarget(screenX, screenY, coordinates);
- if (dropTarget != null) {
- if (mLastDropTarget == dropTarget) {
- dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1],
- (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
- } else {
- if (mLastDropTarget != null) {
- mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
- (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
- }
- dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1],
- (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
- }
- } else {
- if (mLastDropTarget != null) {
- mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
- (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
- }
- }
- mLastDropTarget = dropTarget;
-
- // Scroll, maybe, but not if we're in the delete region.
- boolean inDeleteRegion = false;
- if (mDeleteRegion != null) {
- inDeleteRegion = mDeleteRegion.contains(screenX, screenY);
- }
- //Log.d(Launcher.TAG, "inDeleteRegion=" + inDeleteRegion + " screenX=" + screenX
- // + " mScrollZone=" + mScrollZone);
- if (!inDeleteRegion && screenX < mScrollZone) {
- if (mScrollState == SCROLL_OUTSIDE_ZONE) {
- mScrollState = SCROLL_WAITING_IN_ZONE;
- mScrollRunnable.setDirection(SCROLL_LEFT);
- mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
- }
- } else if (!inDeleteRegion && screenX > scrollView.getWidth() - mScrollZone) {
- if (mScrollState == SCROLL_OUTSIDE_ZONE) {
- mScrollState = SCROLL_WAITING_IN_ZONE;
- mScrollRunnable.setDirection(SCROLL_RIGHT);
- mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
- }
- } else {
- if (mScrollState == SCROLL_WAITING_IN_ZONE) {
- mScrollState = SCROLL_OUTSIDE_ZONE;
- mScrollRunnable.setDirection(SCROLL_RIGHT);
- mHandler.removeCallbacks(mScrollRunnable);
- }
- }
-
+ handleMoveEvent(screenX, screenY);
break;
case MotionEvent.ACTION_UP:
mHandler.removeCallbacks(mScrollRunnable);
@@ -491,13 +594,28 @@ public class DragController {
final ArrayList<DropTarget> dropTargets = mDropTargets;
final int count = dropTargets.size();
for (int i=count-1; i>=0; i--) {
- final DropTarget target = dropTargets.get(i);
+ DropTarget target = dropTargets.get(i);
+ if (!target.isDropEnabled())
+ continue;
+
target.getHitRect(r);
+
+ // Convert the hit rect to screen coordinates
target.getLocationOnScreen(dropCoordinates);
r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop());
+
if (r.contains(x, y)) {
+ DropTarget delegate = target.getDropTargetDelegate(mDragSource,
+ x, y, (int)mTouchOffsetX, (int)mTouchOffsetY, mDragView, mDragInfo);
+ if (delegate != null) {
+ target = delegate;
+ target.getLocationOnScreen(dropCoordinates);
+ }
+
+ // Make dropCoordinates relative to the DropTarget
dropCoordinates[0] = x - dropCoordinates[0];
dropCoordinates[1] = y - dropCoordinates[1];
+
return target;
}
}
@@ -537,15 +655,15 @@ public class DragController {
/**
* Sets the drag listner which will be notified when a drag starts or ends.
*/
- public void setDragListener(DragListener l) {
- mListener = l;
+ public void addDragListener(DragListener l) {
+ mListeners.add(l);
}
/**
* Remove a previously installed drag listener.
*/
public void removeDragListener(DragListener l) {
- mListener = null;
+ mListeners.remove(l);
}
/**
@@ -592,6 +710,8 @@ public class DragController {
mDragScroller.scrollRight();
}
mScrollState = SCROLL_OUTSIDE_ZONE;
+ mDistanceSinceScroll = 0;
+ mDragScroller.onExitScrollArea();
}
}
diff --git a/src/com/android/launcher2/DragLayer.java b/src/com/android/launcher2/DragLayer.java
index c68320764..ab71670a2 100644
--- a/src/com/android/launcher2/DragLayer.java
+++ b/src/com/android/launcher2/DragLayer.java
@@ -18,13 +18,13 @@ package com.android.launcher2;
import android.content.Context;
import android.util.AttributeSet;
-import android.view.MotionEvent;
import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
/**
- * A ViewGroup that coordinated dragging across its dscendants
+ * A ViewGroup that coordinates dragging across its descendants
*/
public class DragLayer extends FrameLayout {
DragController mDragController;
@@ -33,7 +33,7 @@ public class DragLayer extends FrameLayout {
* Used to create a new DragLayer from XML.
*
* @param context The application's context.
- * @param attrs The attribtues set containing the Workspace's customization values.
+ * @param attrs The attributes set containing the Workspace's customization values.
*/
public DragLayer(Context context, AttributeSet attrs) {
super(context, attrs);
diff --git a/src/com/android/launcher2/DragScroller.java b/src/com/android/launcher2/DragScroller.java
index c3c251c2f..6af9c3032 100644
--- a/src/com/android/launcher2/DragScroller.java
+++ b/src/com/android/launcher2/DragScroller.java
@@ -23,4 +23,16 @@ package com.android.launcher2;
public interface DragScroller {
void scrollLeft();
void scrollRight();
+
+ /**
+ * The touch point has entered the scroll area; a scroll is imminent.
+ *
+ * @param direction The scroll direction
+ */
+ void onEnterScrollArea(int direction);
+
+ /**
+ * The touch point has left the scroll area.
+ */
+ void onExitScrollArea();
}
diff --git a/src/com/android/launcher2/DragView.java b/src/com/android/launcher2/DragView.java
index 248712ed3..ca0e7b443 100644
--- a/src/com/android/launcher2/DragView.java
+++ b/src/com/android/launcher2/DragView.java
@@ -17,33 +17,33 @@
package com.android.launcher2;
+import com.android.launcher.R;
+
import android.content.Context;
-import android.content.res.TypedArray;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.Point;
import android.os.IBinder;
-import android.util.AttributeSet;
-import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
-import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
public class DragView extends View implements TweenCallback {
- // Number of pixels to add to the dragged item for scaling. Should be even for pixel alignment.
- private static final int DRAG_SCALE = 40;
-
private Bitmap mBitmap;
private Paint mPaint;
private int mRegistrationX;
private int mRegistrationY;
+ private int mDragRegionLeft = 0;
+ private int mDragRegionTop = 0;
+ private int mDragRegionWidth;
+ private int mDragRegionHeight;
+
SymmetricalLinearTween mTween;
private float mScale;
private float mAnimationScale = 1.0f;
@@ -66,20 +66,63 @@ public class DragView extends View implements TweenCallback {
int left, int top, int width, int height) {
super(context);
+ final Resources res = getResources();
+ final int dragScale = res.getInteger(R.integer.config_dragViewExtraPixels);
+
mWindowManager = WindowManagerImpl.getDefault();
mTween = new SymmetricalLinearTween(false, 110 /*ms duration*/, this);
Matrix scale = new Matrix();
float scaleFactor = width;
- scaleFactor = mScale = (scaleFactor + DRAG_SCALE) / scaleFactor;
+ scaleFactor = mScale = (scaleFactor + dragScale) / scaleFactor;
scale.setScale(scaleFactor, scaleFactor);
mBitmap = Bitmap.createBitmap(bitmap, left, top, width, height, scale, true);
+ setDragRegion(0, 0, width, height);
// The point in our scaled bitmap that the touch events are located
- mRegistrationX = registrationX + (DRAG_SCALE / 2);
- mRegistrationY = registrationY + (DRAG_SCALE / 2);
+ mRegistrationX = registrationX + res.getInteger(R.integer.config_dragViewOffsetX);
+ mRegistrationY = registrationY + res.getInteger(R.integer.config_dragViewOffsetY);
+ }
+
+ public void setDragRegion(int left, int top, int width, int height) {
+ mDragRegionLeft = left;
+ mDragRegionTop = top;
+ mDragRegionWidth = width;
+ mDragRegionHeight = height;
+ }
+
+ public int getScaledDragRegionXOffset() {
+ return -(int)((mScale - 1.0f) * mDragRegionWidth / 2);
+ }
+
+ public int getScaledDragRegionWidth() {
+ return (int)(mScale * mDragRegionWidth);
+ }
+
+ public int getScaledDragRegionYOffset() {
+ return -(int)((mScale - 1.0f) * mDragRegionHeight / 2);
+ }
+
+ public int getScaledDragRegionHeight() {
+ return (int)(mScale * mDragRegionWidth);
+ }
+
+ public int getDragRegionLeft() {
+ return mDragRegionLeft;
+ }
+
+ public int getDragRegionTop() {
+ return mDragRegionTop;
+ }
+
+ public int getDragRegionWidth() {
+ return mDragRegionWidth;
+ }
+
+ public int getDragRegionHeight() {
+ return mDragRegionHeight;
}
@Override
diff --git a/src/com/android/launcher2/DropTarget.java b/src/com/android/launcher2/DropTarget.java
index 72eb33094..308dbbee3 100644
--- a/src/com/android/launcher2/DropTarget.java
+++ b/src/com/android/launcher2/DropTarget.java
@@ -23,6 +23,12 @@ import android.graphics.Rect;
*
*/
public interface DropTarget {
+ /**
+ * Used to temporarily disable certain drop targets
+ *
+ * @return boolean specifying whether this drop target is currently enabled
+ */
+ boolean isDropEnabled();
/**
* Handle an object being dropped on the DropTarget
@@ -51,27 +57,28 @@ public interface DropTarget {
DragView dragView, Object dragInfo);
/**
- * Check if a drop action can occur at, or near, the requested location.
- * This may be called repeatedly during a drag, so any calls should return
- * quickly.
- *
+ * Allows a DropTarget to delegate drag and drop events to another object.
+ *
+ * Most subclasses will should just return null from this method.
+ *
* @param source DragSource where the drag started
* @param x X coordinate of the drop location
* @param y Y coordinate of the drop location
- * @param xOffset Horizontal offset with the object being dragged where the
- * original touch happened
- * @param yOffset Vertical offset with the object being dragged where the
- * original touch happened
+ * @param xOffset Horizontal offset with the object being dragged where the original
+ * touch happened
+ * @param yOffset Vertical offset with the object being dragged where the original
+ * touch happened
* @param dragView The DragView that's being dragged around on screen.
* @param dragInfo Data associated with the object being dragged
- * @return True if the drop will be accepted, false otherwise.
+ *
+ * @return The DropTarget to delegate to, or null to not delegate to another object.
*/
- boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
+ DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
DragView dragView, Object dragInfo);
/**
- * Estimate the surface area where this object would land if dropped at the
- * given location.
+ * Check if a drop action can occur at, or near, the requested location.
+ * This will be called just before onDrop.
*
* @param source DragSource where the drag started
* @param x X coordinate of the drop location
@@ -82,13 +89,10 @@ public interface DropTarget {
* original touch happened
* @param dragView The DragView that's being dragged around on screen.
* @param dragInfo Data associated with the object being dragged
- * @param recycle {@link Rect} object to be possibly recycled.
- * @return Estimated area that would be occupied if object was dropped at
- * the given location. Should return null if no estimate is found,
- * or if this target doesn't provide estimations.
+ * @return True if the drop will be accepted, false otherwise.
*/
- Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset,
- DragView dragView, Object dragInfo, Rect recycle);
+ boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo);
// These methods are implemented in Views
void getHitRect(Rect outRect);
diff --git a/src/com/android/launcher2/FastBitmapDrawable.java b/src/com/android/launcher2/FastBitmapDrawable.java
index 226d6d8d8..1cafa09a7 100644
--- a/src/com/android/launcher2/FastBitmapDrawable.java
+++ b/src/com/android/launcher2/FastBitmapDrawable.java
@@ -17,6 +17,7 @@
package com.android.launcher2;
import android.graphics.drawable.Drawable;
+import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -26,6 +27,7 @@ class FastBitmapDrawable extends Drawable {
private Bitmap mBitmap;
private int mWidth;
private int mHeight;
+ private final Paint mPaint = new Paint();
FastBitmapDrawable(Bitmap b) {
mBitmap = b;
@@ -39,7 +41,7 @@ class FastBitmapDrawable extends Drawable {
@Override
public void draw(Canvas canvas) {
- canvas.drawBitmap(mBitmap, 0.0f, 0.0f, null);
+ canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
}
@Override
@@ -49,6 +51,7 @@ class FastBitmapDrawable extends Drawable {
@Override
public void setAlpha(int alpha) {
+ mPaint.setAlpha(alpha);
}
@Override
diff --git a/src/com/android/launcher2/Folder.java b/src/com/android/launcher2/Folder.java
index 7ff83284e..018b28456 100644
--- a/src/com/android/launcher2/Folder.java
+++ b/src/com/android/launcher2/Folder.java
@@ -21,11 +21,11 @@ import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
+import android.widget.AbsListView;
import android.widget.AdapterView;
+import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
-import android.widget.AbsListView;
-import android.widget.BaseAdapter;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
@@ -148,7 +148,7 @@ public class Folder extends LinearLayout implements DragSource, OnItemLongClickL
void onClose() {
final Workspace workspace = mLauncher.getWorkspace();
- workspace.getChildAt(workspace.getCurrentScreen()).requestFocus();
+ workspace.getChildAt(workspace.getCurrentPage()).requestFocus();
}
void bind(FolderInfo info) {
diff --git a/src/com/android/launcher2/FolderIcon.java b/src/com/android/launcher2/FolderIcon.java
index 0013644ab..dae84aac9 100644
--- a/src/com/android/launcher2/FolderIcon.java
+++ b/src/com/android/launcher2/FolderIcon.java
@@ -29,7 +29,7 @@ import com.android.launcher.R;
/**
* An icon that can appear on in the workspace representing an {@link UserFolder}.
*/
-public class FolderIcon extends BubbleTextView implements DropTarget {
+public class FolderIcon extends DimmableBubbleTextView implements DropTarget {
private UserFolderInfo mInfo;
private Launcher mLauncher;
private Drawable mCloseIcon;
@@ -43,15 +43,19 @@ public class FolderIcon extends BubbleTextView implements DropTarget {
super(context);
}
+ public boolean isDropEnabled() {
+ return !((Workspace)getParent().getParent()).isSmall();
+ }
+
static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
- UserFolderInfo folderInfo) {
+ UserFolderInfo folderInfo, IconCache iconCache) {
FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
final Resources resources = launcher.getResources();
- Drawable d = resources.getDrawable(R.drawable.ic_launcher_folder);
+ Drawable d = iconCache.getFullResIcon(resources, R.drawable.ic_launcher_folder);
icon.mCloseIcon = d;
- icon.mOpenIcon = resources.getDrawable(R.drawable.ic_launcher_folder_open);
+ icon.mOpenIcon = iconCache.getFullResIcon(resources, R.drawable.ic_launcher_folder_open);
icon.setCompoundDrawablesWithIntrinsicBounds(null, d, null, null);
icon.setText(folderInfo.title);
icon.setTag(folderInfo);
@@ -71,11 +75,6 @@ public class FolderIcon extends BubbleTextView implements DropTarget {
&& item.container != mInfo.id;
}
- public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset,
- DragView dragView, Object dragInfo, Rect recycle) {
- return null;
- }
-
public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
DragView dragView, Object dragInfo) {
ShortcutInfo item;
@@ -91,7 +90,9 @@ public class FolderIcon extends BubbleTextView implements DropTarget {
public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
DragView dragView, Object dragInfo) {
- setCompoundDrawablesWithIntrinsicBounds(null, mOpenIcon, null, null);
+ if (acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) {
+ setCompoundDrawablesWithIntrinsicBounds(null, mOpenIcon, null, null);
+ }
}
public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
@@ -102,4 +103,10 @@ public class FolderIcon extends BubbleTextView implements DropTarget {
DragView dragView, Object dragInfo) {
setCompoundDrawablesWithIntrinsicBounds(null, mCloseIcon, null, null);
}
+
+ @Override
+ public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo) {
+ return null;
+ }
}
diff --git a/src/com/android/launcher2/HolographicOutlineHelper.java b/src/com/android/launcher2/HolographicOutlineHelper.java
new file mode 100644
index 000000000..658490acc
--- /dev/null
+++ b/src/com/android/launcher2/HolographicOutlineHelper.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2008 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.launcher2;
+
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.MaskFilter;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.TableMaskFilter;
+
+public class HolographicOutlineHelper {
+ private final Paint mHolographicPaint = new Paint();
+ private final Paint mBlurPaint = new Paint();
+ private final Paint mErasePaint = new Paint();
+
+ public static final int OUTER_BLUR_RADIUS;
+
+ private static final BlurMaskFilter sThickOuterBlurMaskFilter;
+ private static final BlurMaskFilter sMediumOuterBlurMaskFilter;
+ private static final BlurMaskFilter sThinOuterBlurMaskFilter;
+ private static final BlurMaskFilter sThickInnerBlurMaskFilter;
+
+ static {
+ final float scale = LauncherApplication.getScreenDensity();
+
+ OUTER_BLUR_RADIUS = (int) (scale * 6.0f);
+
+ sThickOuterBlurMaskFilter = new BlurMaskFilter(OUTER_BLUR_RADIUS,
+ BlurMaskFilter.Blur.OUTER);
+ sMediumOuterBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.OUTER);
+ sThinOuterBlurMaskFilter = new BlurMaskFilter(scale * 1.0f, BlurMaskFilter.Blur.OUTER);
+ sThickInnerBlurMaskFilter = new BlurMaskFilter(scale * 4.0f, BlurMaskFilter.Blur.NORMAL);
+ }
+
+ private static final MaskFilter sFineClipTable = TableMaskFilter.CreateClipTable(0, 20);
+ private static final MaskFilter sCoarseClipTable = TableMaskFilter.CreateClipTable(0, 200);
+
+ private int[] mTempOffset = new int[2];
+
+ HolographicOutlineHelper() {
+ mHolographicPaint.setFilterBitmap(true);
+ mHolographicPaint.setAntiAlias(true);
+ mBlurPaint.setFilterBitmap(true);
+ mBlurPaint.setAntiAlias(true);
+ mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ mErasePaint.setFilterBitmap(true);
+ mErasePaint.setAntiAlias(true);
+ }
+
+ /**
+ * Returns the interpolated holographic highlight alpha for the effect we want when scrolling
+ * pages.
+ */
+ public float highlightAlphaInterpolator(float r) {
+ float maxAlpha = 0.8f;
+ return (float) Math.pow(maxAlpha * (1.0f - r), 1.5f);
+ }
+
+ /**
+ * Returns the interpolated view alpha for the effect we want when scrolling pages.
+ */
+ public float viewAlphaInterpolator(float r) {
+ final float pivot = 0.95f;
+ if (r < pivot) {
+ return (float) Math.pow(r / pivot, 1.5f);
+ } else {
+ return 1.0f;
+ }
+ }
+
+ /**
+ * Apply an outer blur to the given bitmap.
+ * You should use OUTER_BLUR_RADIUS to ensure that the bitmap is big enough to draw
+ * the blur without clipping.
+ */
+ void applyOuterBlur(Bitmap bitmap, Canvas canvas, int color) {
+ mBlurPaint.setMaskFilter(sThickOuterBlurMaskFilter);
+ Bitmap glow = bitmap.extractAlpha(mBlurPaint, mTempOffset);
+
+ // Use the clip table to make the glow heavier closer to the outline
+ mHolographicPaint.setMaskFilter(sCoarseClipTable);
+ mHolographicPaint.setAlpha(150);
+ mHolographicPaint.setColor(color);
+ canvas.drawBitmap(glow, mTempOffset[0], mTempOffset[1], mHolographicPaint);
+ glow.recycle();
+ }
+
+ /**
+ * Draws a solid outline around a bitmap, erasing the original pixels.
+ *
+ * @param bitmap The bitmap to modify
+ * @param canvas A canvas on the bitmap
+ * @param color The color to draw the outline and glow in
+ * @param removeOrig If true, punch out the original pixels to just leave the outline
+ */
+ void applyExpensiveOuterOutline(Bitmap bitmap, Canvas canvas, int color, boolean removeOrig) {
+ Bitmap originalImage = null;
+ if (removeOrig) {
+ originalImage = bitmap.extractAlpha();
+ }
+
+ // Compute an outer blur on the original bitmap
+ mBlurPaint.setMaskFilter(sMediumOuterBlurMaskFilter);
+ Bitmap outline = bitmap.extractAlpha(mBlurPaint, mTempOffset);
+
+ // Paint the blurred bitmap back into the canvas. Using the clip table causes any alpha
+ // pixels above a certain threshold to be rounded up to be fully opaque. This gives the
+ // effect of a thick outline, with a slight blur on the edge
+ mHolographicPaint.setColor(color);
+ mHolographicPaint.setMaskFilter(sFineClipTable);
+ canvas.drawBitmap(outline, mTempOffset[0], mTempOffset[1], mHolographicPaint);
+ outline.recycle();
+
+ if (removeOrig) {
+ // Finally, punch out the original pixels, leaving just the outline
+ canvas.drawBitmap(originalImage, 0, 0, mErasePaint);
+ originalImage.recycle();
+ }
+ }
+
+ /**
+ * Applies a more expensive and accurate outline to whatever is currently drawn in a specified
+ * bitmap.
+ */
+ void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
+ int outlineColor) {
+ // calculate the outer blur first
+ mBlurPaint.setMaskFilter(sThickOuterBlurMaskFilter);
+ int[] outerBlurOffset = new int[2];
+ Bitmap thickOuterBlur = srcDst.extractAlpha(mBlurPaint, outerBlurOffset);
+ mBlurPaint.setMaskFilter(sThinOuterBlurMaskFilter);
+ int[] thinOuterBlurOffset = new int[2];
+ Bitmap thinOuterBlur = srcDst.extractAlpha(mBlurPaint, thinOuterBlurOffset);
+
+ // calculate the inner blur
+ srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT);
+ mBlurPaint.setMaskFilter(sThickInnerBlurMaskFilter);
+ int[] thickInnerBlurOffset = new int[2];
+ Bitmap thickInnerBlur = srcDst.extractAlpha(mBlurPaint, thickInnerBlurOffset);
+
+ // mask out the inner blur
+ srcDstCanvas.setBitmap(thickInnerBlur);
+ srcDstCanvas.drawBitmap(srcDst, -thickInnerBlurOffset[0],
+ -thickInnerBlurOffset[1], mErasePaint);
+ srcDstCanvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(),
+ mErasePaint);
+ srcDstCanvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1],
+ mErasePaint);
+
+ // draw the inner and outer blur
+ srcDstCanvas.setBitmap(srcDst);
+ srcDstCanvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
+ mHolographicPaint.setColor(color);
+ srcDstCanvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1],
+ mHolographicPaint);
+ srcDstCanvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1],
+ mHolographicPaint);
+
+ // draw the bright outline
+ mHolographicPaint.setColor(outlineColor);
+ srcDstCanvas.drawBitmap(thinOuterBlur, thinOuterBlurOffset[0], thinOuterBlurOffset[1],
+ mHolographicPaint);
+
+ // cleanup
+ thinOuterBlur.recycle();
+ thickOuterBlur.recycle();
+ thickInnerBlur.recycle();
+ }
+}
diff --git a/src/com/android/launcher2/IconCache.java b/src/com/android/launcher2/IconCache.java
index 81a786ca7..ae8c98a60 100644
--- a/src/com/android/launcher2/IconCache.java
+++ b/src/com/android/launcher2/IconCache.java
@@ -16,13 +16,17 @@
package com.android.launcher2;
+import com.android.launcher.R;
+
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
+import android.util.DisplayMetrics;
import java.util.HashMap;
@@ -46,16 +50,49 @@ public class IconCache {
private final Utilities.BubbleText mBubble;
private final HashMap<ComponentName, CacheEntry> mCache =
new HashMap<ComponentName, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY);
+ private int mIconDpi;
public IconCache(LauncherApplication context) {
mContext = context;
mPackageManager = context.getPackageManager();
mBubble = new Utilities.BubbleText(context);
+ if (LauncherApplication.isScreenXLarge()) {
+ mIconDpi = DisplayMetrics.DENSITY_HIGH;
+ } else {
+ mIconDpi = context.getResources().getDisplayMetrics().densityDpi;
+ }
+ // need to set mIconDpi before getting default icon
mDefaultIcon = makeDefaultIcon();
}
+ public Drawable getFullResDefaultActivityIcon() {
+ return getFullResIcon(Resources.getSystem(),
+ com.android.internal.R.drawable.sym_def_app_icon);
+ }
+
+ public Drawable getFullResIcon(Resources resources, int iconId) {
+ return resources.getDrawableForDensity(iconId, mIconDpi);
+ }
+
+ public Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) {
+ Resources resources;
+ try {
+ resources = packageManager.getResourcesForApplication(
+ info.activityInfo.applicationInfo);
+ } catch (PackageManager.NameNotFoundException e) {
+ resources = null;
+ }
+ if (resources != null) {
+ int iconId = info.activityInfo.getIconResource();
+ if (iconId != 0) {
+ return getFullResIcon(resources, iconId);
+ }
+ }
+ return getFullResDefaultActivityIcon();
+ }
+
private Bitmap makeDefaultIcon() {
- Drawable d = mPackageManager.getDefaultActivityIcon();
+ Drawable d = getFullResDefaultActivityIcon();
Bitmap b = Bitmap.createBitmap(Math.max(d.getIntrinsicWidth(), 1),
Math.max(d.getIntrinsicHeight(), 1),
Bitmap.Config.ARGB_8888);
@@ -140,7 +177,7 @@ public class IconCache {
entry.title = info.activityInfo.name;
}
entry.icon = Utilities.createIconBitmap(
- info.activityInfo.loadIcon(mPackageManager), mContext);
+ getFullResIcon(info, mPackageManager), mContext);
}
return entry;
}
diff --git a/src/com/android/launcher2/InstallShortcutReceiver.java b/src/com/android/launcher2/InstallShortcutReceiver.java
index 3fc568b9f..ac50f6605 100644
--- a/src/com/android/launcher2/InstallShortcutReceiver.java
+++ b/src/com/android/launcher2/InstallShortcutReceiver.java
@@ -16,19 +16,23 @@
package com.android.launcher2;
+import java.util.ArrayList;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.ContentResolver;
-import android.database.Cursor;
import android.widget.Toast;
import com.android.launcher.R;
public class InstallShortcutReceiver extends BroadcastReceiver {
- private static final String ACTION_INSTALL_SHORTCUT =
+ public static final String ACTION_INSTALL_SHORTCUT =
"com.android.launcher.action.INSTALL_SHORTCUT";
+ // A mime-type representing shortcut data
+ public static final String SHORTCUT_MIMETYPE =
+ "com.android.launcher/shortcut";
+
private final int[] mCoordinates = new int[2];
public void onReceive(Context context, Intent data) {
@@ -50,11 +54,6 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
if (findEmptyCell(context, mCoordinates, screen)) {
- CellLayout.CellInfo cell = new CellLayout.CellInfo();
- cell.cellX = mCoordinates[0];
- cell.cellY = mCoordinates[1];
- cell.screen = screen;
-
Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
if (intent.getAction() == null) {
@@ -66,7 +65,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
if (duplicate || !LauncherModel.shortcutExists(context, name, intent)) {
((LauncherApplication)context.getApplicationContext()).getModel()
- .addShortcut(context, data, cell, true);
+ .addShortcut(context, data, screen, mCoordinates[0], mCoordinates[1], true);
Toast.makeText(context, context.getString(R.string.shortcut_installed, name),
Toast.LENGTH_SHORT).show();
} else {
@@ -84,40 +83,26 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
}
private static boolean findEmptyCell(Context context, int[] xy, int screen) {
- final int xCount = Launcher.NUMBER_CELLS_X;
- final int yCount = Launcher.NUMBER_CELLS_Y;
-
+ final int xCount = LauncherModel.getCellCountX();
+ final int yCount = LauncherModel.getCellCountY();
boolean[][] occupied = new boolean[xCount][yCount];
- final ContentResolver cr = context.getContentResolver();
- Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
- new String[] { LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY,
- LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY },
- LauncherSettings.Favorites.SCREEN + "=?",
- new String[] { String.valueOf(screen) }, null);
-
- final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
- final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
- final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
- final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
-
- try {
- while (c.moveToNext()) {
- int cellX = c.getInt(cellXIndex);
- int cellY = c.getInt(cellYIndex);
- int spanX = c.getInt(spanXIndex);
- int spanY = c.getInt(spanYIndex);
-
+ ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context);
+ ItemInfo item = null;
+ int cellX, cellY, spanX, spanY;
+ for (int i = 0; i < items.size(); ++i) {
+ item = items.get(i);
+ if (item.screen == screen) {
+ cellX = item.cellX;
+ cellY = item.cellY;
+ spanX = item.spanX;
+ spanY = item.spanY;
for (int x = cellX; x < cellX + spanX && x < xCount; x++) {
for (int y = cellY; y < cellY + spanY && y < yCount; y++) {
occupied[x][y] = true;
}
}
}
- } catch (Exception e) {
- return false;
- } finally {
- c.close();
}
return CellLayout.findVacantCell(xy, 1, 1, xCount, yCount, occupied);
diff --git a/src/com/android/launcher2/InstallWidgetReceiver.java b/src/com/android/launcher2/InstallWidgetReceiver.java
new file mode 100644
index 000000000..970c6a5be
--- /dev/null
+++ b/src/com/android/launcher2/InstallWidgetReceiver.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import java.util.List;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import com.android.launcher.R;
+
+
+/**
+ * We will likely flesh this out later, to handle allow external apps to place widgets, but for now,
+ * we just want to expose the action around for checking elsewhere.
+ */
+public class InstallWidgetReceiver {
+ public static final String ACTION_INSTALL_WIDGET =
+ "com.android.launcher.action.INSTALL_WIDGET";
+ public static final String ACTION_SUPPORTS_CLIPDATA_MIMETYPE =
+ "com.android.launcher.action.SUPPORTS_CLIPDATA_MIMETYPE";
+
+ // Currently not exposed. Put into Intent when we want to make it public.
+ // TEMP: Should we call this "EXTRA_APPWIDGET_PROVIDER"?
+ public static final String EXTRA_APPWIDGET_COMPONENT =
+ "com.android.launcher.extra.widget.COMPONENT";
+ public static final String EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE =
+ "com.android.launcher.extra.widget.CONFIGURATION_DATA_MIME_TYPE";
+ public static final String EXTRA_APPWIDGET_CONFIGURATION_DATA =
+ "com.android.launcher.extra.widget.CONFIGURATION_DATA";
+
+ /**
+ * A simple data class that contains per-item information that the adapter below can reference.
+ */
+ public static class WidgetMimeTypeHandlerData {
+ public ResolveInfo resolveInfo;
+ public AppWidgetProviderInfo widgetInfo;
+
+ public WidgetMimeTypeHandlerData(ResolveInfo rInfo, AppWidgetProviderInfo wInfo) {
+ resolveInfo = rInfo;
+ widgetInfo = wInfo;
+ }
+ }
+
+ /**
+ * The ListAdapter which presents all the valid widgets that can be created for a given drop.
+ */
+ public static class WidgetListAdapter implements ListAdapter, DialogInterface.OnClickListener {
+ private LayoutInflater mInflater;
+ private Launcher mLauncher;
+ private String mMimeType;
+ private ClipData mClipData;
+ private List<WidgetMimeTypeHandlerData> mActivities;
+ private CellLayout mTargetLayout;
+ private int mTargetLayoutScreen;
+ private int[] mTargetLayoutPos;
+
+ public WidgetListAdapter(Launcher l, String mimeType, ClipData data,
+ List<WidgetMimeTypeHandlerData> list, CellLayout target,
+ int targetScreen, int[] targetPos) {
+ mLauncher = l;
+ mMimeType = mimeType;
+ mClipData = data;
+ mActivities = list;
+ mTargetLayout = target;
+ mTargetLayoutScreen = targetScreen;
+ mTargetLayoutPos = targetPos;
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver observer) {
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ }
+
+ @Override
+ public int getCount() {
+ return mActivities.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final Context context = parent.getContext();
+ final PackageManager packageManager = context.getPackageManager();
+
+ // Lazy-create inflater
+ if (mInflater == null) {
+ mInflater = LayoutInflater.from(context);
+ }
+
+ // Use the convert-view where possible
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.external_widget_drop_list_item, parent,
+ false);
+ }
+
+ final WidgetMimeTypeHandlerData data = mActivities.get(position);
+ final ResolveInfo resolveInfo = data.resolveInfo;
+ final AppWidgetProviderInfo widgetInfo = data.widgetInfo;
+
+ // Set the icon
+ Drawable d = resolveInfo.loadIcon(packageManager);
+ ImageView i = (ImageView) convertView.findViewById(R.id.provider_icon);
+ i.setImageDrawable(d);
+
+ // Set the text
+ final CharSequence component = resolveInfo.loadLabel(packageManager);
+ final int[] widgetSpan = new int[2];
+ mTargetLayout.rectToCell(widgetInfo.minWidth, widgetInfo.minHeight, widgetSpan);
+ TextView t = (TextView) convertView.findViewById(R.id.provider);
+ t.setText(context.getString(R.string.external_drop_widget_pick_format,
+ component, widgetSpan[0], widgetSpan[1]));
+
+ return convertView;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return 0;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return mActivities.isEmpty();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return true;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final LauncherModel model = mLauncher.getModel();
+ final AppWidgetProviderInfo widgetInfo = mActivities.get(which).widgetInfo;
+
+ final PendingAddWidgetInfo createInfo = new PendingAddWidgetInfo(widgetInfo, mMimeType,
+ mClipData);
+ mLauncher.addAppWidgetFromDrop(createInfo, mTargetLayoutScreen, mTargetLayoutPos);
+ }
+ }
+}
diff --git a/src/com/android/launcher2/InterruptibleInOutAnimator.java b/src/com/android/launcher2/InterruptibleInOutAnimator.java
new file mode 100644
index 000000000..570b9e773
--- /dev/null
+++ b/src/com/android/launcher2/InterruptibleInOutAnimator.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.util.Log;
+
+/**
+ * A convenience class for two-way animations, e.g. a fadeIn/fadeOut animation.
+ * With a regular ValueAnimator, if you call reverse to show the 'out' animation, you'll get
+ * a frame-by-frame mirror of the 'in' animation -- i.e., the interpolated values will
+ * be exactly reversed. Using this class, both the 'in' and the 'out' animation use the
+ * interpolator in the same direction.
+ */
+public class InterruptibleInOutAnimator {
+ private long mOriginalDuration;
+ private float mOriginalFromValue;
+ private float mOriginalToValue;
+ private ValueAnimator mAnimator;
+
+ private boolean mFirstRun = true;
+
+ private Object mTag = null;
+
+ private static final int STOPPED = 0;
+ private static final int IN = 1;
+ private static final int OUT = 2;
+
+ // TODO: This isn't really necessary, but is here to help diagnose a bug in the drag viz
+ private int mDirection = STOPPED;
+
+ public InterruptibleInOutAnimator(long duration, float fromValue, float toValue) {
+ mAnimator = ValueAnimator.ofFloat(fromValue, toValue).setDuration(duration);
+ mOriginalDuration = duration;
+ mOriginalFromValue = fromValue;
+ mOriginalToValue = toValue;
+
+ mAnimator.addListener(new LauncherAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEndOrCancel(Animator animation) {
+ mDirection = STOPPED;
+ }
+ });
+ }
+
+ private void animate(int direction) {
+ final long currentPlayTime = mAnimator.getCurrentPlayTime();
+ final float toValue = (direction == IN) ? mOriginalToValue : mOriginalFromValue;
+ final float startValue = mFirstRun ? mOriginalFromValue :
+ ((Float) mAnimator.getAnimatedValue()).floatValue();
+
+ // Make sure it's stopped before we modify any values
+ cancel();
+
+ // TODO: We don't really need to do the animation if startValue == toValue, but
+ // somehow that doesn't seem to work, possibly a quirk of the animation framework
+ mDirection = direction;
+
+ // Ensure we don't calculate a non-sensical duration
+ long duration = mOriginalDuration - currentPlayTime;
+ mAnimator.setDuration(Math.max(0, Math.min(duration, mOriginalDuration)));
+
+ mAnimator.setFloatValues(startValue, toValue);
+ mAnimator.start();
+ mFirstRun = false;
+ }
+
+ public void cancel() {
+ mAnimator.cancel();
+ mDirection = STOPPED;
+ }
+
+ public void end() {
+ mAnimator.end();
+ mDirection = STOPPED;
+ }
+
+ /**
+ * Return true when the animation is not running and it hasn't even been started.
+ */
+ public boolean isStopped() {
+ return mDirection == STOPPED;
+ }
+
+ /**
+ * This is the equivalent of calling Animator.start(), except that it can be called when
+ * the animation is running in the opposite direction, in which case we reverse
+ * direction and animate for a correspondingly shorter duration.
+ */
+ public void animateIn() {
+ animate(IN);
+ }
+
+ /**
+ * This is the roughly the equivalent of calling Animator.reverse(), except that it uses the
+ * same interpolation curve as animateIn(), rather than mirroring it. Also, like animateIn(),
+ * if the animation is currently running in the opposite direction, we reverse
+ * direction and animate for a correspondingly shorter duration.
+ */
+ public void animateOut() {
+ animate(OUT);
+ }
+
+ public void setTag(Object tag) {
+ mTag = tag;
+ }
+
+ public Object getTag() {
+ return mTag;
+ }
+
+ public ValueAnimator getAnimator() {
+ return mAnimator;
+ }
+}
diff --git a/src/com/android/launcher2/ItemInfo.java b/src/com/android/launcher2/ItemInfo.java
index a96d9aeb2..dc4575062 100644
--- a/src/com/android/launcher2/ItemInfo.java
+++ b/src/com/android/launcher2/ItemInfo.java
@@ -112,6 +112,11 @@ class ItemInfo {
}
}
+ void updateValuesWithCoordinates(ContentValues values, int cellX, int cellY) {
+ values.put(LauncherSettings.Favorites.CELLX, cellX);
+ values.put(LauncherSettings.Favorites.CELLY, cellY);
+ }
+
static byte[] flattenBitmap(Bitmap bitmap) {
// Try go guesstimate how much space the icon will take when serialized
// to avoid unnecessary allocations/copies during the write.
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index b361ab5c1..549303d03 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -1,3 +1,4 @@
+
/*
* Copyright (C) 2008 The Android Open Source Project
*
@@ -16,16 +17,31 @@
package com.android.launcher2;
-import com.android.common.Search;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.SearchManager;
import android.app.StatusBarManager;
import android.app.WallpaperManager;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -35,16 +51,17 @@ import android.content.Intent.ShortcutIconResource;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.Bitmap;
-import android.graphics.Rect;
import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
+import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -54,6 +71,7 @@ import android.os.Parcelable;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.LiveFolders;
+import android.provider.Settings;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -65,34 +83,33 @@ import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
+import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
-import android.widget.TextView;
-import android.widget.Toast;
import android.widget.ImageView;
-import android.widget.PopupWindow;
import android.widget.LinearLayout;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.HashMap;
-import java.io.DataOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.DataInputStream;
+import android.widget.PopupWindow;
+import android.widget.TabHost;
+import android.widget.TabHost.OnTabChangeListener;
+import android.widget.TabHost.TabContentFactory;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.android.common.Search;
import com.android.launcher.R;
/**
* Default launcher application.
*/
public final class Launcher extends Activity
- implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, AllAppsView.Watcher {
+ implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
+ AllAppsView.Watcher, View.OnTouchListener {
static final String TAG = "Launcher";
static final boolean LOGD = false;
@@ -100,8 +117,6 @@ public final class Launcher extends Activity
static final boolean DEBUG_WIDGETS = false;
static final boolean DEBUG_USER_INTERFACE = false;
- private static final int WALLPAPER_SCREENS_SPAN = 2;
-
private static final int MENU_GROUP_ADD = 1;
private static final int MENU_GROUP_WALLPAPER = MENU_GROUP_ADD + 1;
@@ -125,8 +140,6 @@ public final class Launcher extends Activity
static final int SCREEN_COUNT = 5;
static final int DEFAULT_SCREEN = 2;
- static final int NUMBER_CELLS_X = 4;
- static final int NUMBER_CELLS_Y = 4;
static final int DIALOG_CREATE_SHORTCUT = 1;
static final int DIALOG_RENAME_FOLDER = 2;
@@ -135,8 +148,8 @@ public final class Launcher extends Activity
// Type: int
private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
- // Type: boolean
- private static final String RUNTIME_STATE_ALL_APPS_FOLDER = "launcher.all_apps_folder";
+ // Type: int
+ private static final String RUNTIME_STATE = "launcher.state";
// Type: long
private static final String RUNTIME_STATE_USER_FOLDERS = "launcher.user_folder";
// Type: int
@@ -145,21 +158,24 @@ public final class Launcher extends Activity
private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cellX";
// Type: int
private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cellY";
- // Type: int
- private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_spanX";
- // Type: int
- private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_spanY";
- // Type: int
- private static final String RUNTIME_STATE_PENDING_ADD_COUNT_X = "launcher.add_countX";
- // Type: int
- private static final String RUNTIME_STATE_PENDING_ADD_COUNT_Y = "launcher.add_countY";
- // Type: int[]
- private static final String RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS = "launcher.add_occupied_cells";
// Type: boolean
private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
// Type: long
private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
+ // tags for the customization tabs
+ private static final String WIDGETS_TAG = "widgets";
+ private static final String APPLICATIONS_TAG = "applications";
+ private static final String SHORTCUTS_TAG = "shortcuts";
+ private static final String WALLPAPERS_TAG = "wallpapers";
+
+ private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
+
+ /** The different states that Launcher can be in. */
+ private enum State { WORKSPACE, ALL_APPS, CUSTOMIZE, OVERVIEW };
+ private State mState = State.WORKSPACE;
+ private AnimatorSet mStateAnimation;
+
static final int APPWIDGET_HOST_ID = 1024;
private static final Object sLock = new Object();
@@ -177,14 +193,21 @@ public final class Launcher extends Activity
private AppWidgetManager mAppWidgetManager;
private LauncherAppWidgetHost mAppWidgetHost;
- private CellLayout.CellInfo mAddItemCellInfo;
- private CellLayout.CellInfo mMenuAddInfo;
- private final int[] mCellCoordinates = new int[2];
+ private int mAddScreen = -1;
+ private int mAddIntersectCellX = -1;
+ private int mAddIntersectCellY = -1;
+ private int[] mAddDropPosition;
+ private int[] mTmpAddItemCellCoordinates = new int[2];
+
private FolderInfo mFolderInfo;
private DeleteZone mDeleteZone;
private HandleView mHandleView;
private AllAppsView mAllAppsGrid;
+ private TabHost mHomeCustomizationDrawer;
+
+ private PagedView mAllAppsPagedView = null;
+ private CustomizePagedView mCustomizePagedView = null;
private Bundle mSavedState;
@@ -211,16 +234,23 @@ public final class Launcher extends Activity
private ImageView mNextView;
// Hotseats (quick-launch icons next to AllApps)
- private static final int NUM_HOTSEATS = 2;
private String[] mHotseatConfig = null;
private Intent[] mHotseats = null;
private Drawable[] mHotseatIcons = null;
private CharSequence[] mHotseatLabels = null;
+ private Intent mAppMarketIntent = null;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (LauncherApplication.isInPlaceRotationEnabled()) {
+ // hide the status bar (temporary until we get the status bar design figured out)
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
+ }
+
LauncherApplication app = ((LauncherApplication)getApplication());
mModel = app.setLauncher(this);
mIconCache = app.getIconCache();
@@ -239,8 +269,70 @@ public final class Launcher extends Activity
loadHotseats();
checkForLocaleChange();
setWallpaperDimension();
-
setContentView(R.layout.launcher);
+ mHomeCustomizationDrawer = (TabHost) findViewById(com.android.internal.R.id.tabhost);
+ if (mHomeCustomizationDrawer != null) {
+ mHomeCustomizationDrawer.setup();
+
+ // share the same customization workspace across all the tabs
+ mCustomizePagedView = (CustomizePagedView) mInflater.inflate(
+ R.layout.customization_drawer, mHomeCustomizationDrawer, false);
+ TabContentFactory contentFactory = new TabContentFactory() {
+ public View createTabContent(String tag) {
+ return mCustomizePagedView;
+ }
+ };
+
+ String widgetsLabel = getString(R.string.widgets_tab_label);
+ mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(WIDGETS_TAG)
+ .setIndicator(widgetsLabel).setContent(contentFactory));
+ String applicationsLabel = getString(R.string.applications_tab_label);
+ mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(APPLICATIONS_TAG)
+ .setIndicator(applicationsLabel).setContent(contentFactory));
+ String wallpapersLabel = getString(R.string.wallpapers_tab_label);
+ mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(WALLPAPERS_TAG)
+ .setIndicator(wallpapersLabel).setContent(contentFactory));
+ String shortcutsLabel = getString(R.string.shortcuts_tab_label);
+ mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(SHORTCUTS_TAG)
+ .setIndicator(shortcutsLabel).setContent(contentFactory));
+
+ mHomeCustomizationDrawer.setOnTabChangedListener(new OnTabChangeListener() {
+ public void onTabChanged(String tabId) {
+ // animate the changing of the tab content by fading pages in and out
+ final int duration = 150;
+ final float alpha = mCustomizePagedView.getAlpha();
+ ValueAnimator alphaAnim = ObjectAnimator.ofFloat(mCustomizePagedView,
+ "alpha", alpha, 0.0f);
+ alphaAnim.setDuration(duration);
+ alphaAnim.addListener(new LauncherAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEndOrCancel(Animator animation) {
+ String tag = mHomeCustomizationDrawer.getCurrentTabTag();
+ if (tag == WIDGETS_TAG) {
+ mCustomizePagedView.setCustomizationFilter(
+ CustomizePagedView.CustomizationType.WidgetCustomization);
+ } else if (tag == APPLICATIONS_TAG) {
+ mCustomizePagedView.setCustomizationFilter(
+ CustomizePagedView.CustomizationType.ApplicationCustomization);
+ } else if (tag == WALLPAPERS_TAG) {
+ mCustomizePagedView.setCustomizationFilter(
+ CustomizePagedView.CustomizationType.WallpaperCustomization);
+ } else if (tag == SHORTCUTS_TAG) {
+ mCustomizePagedView.setCustomizationFilter(
+ CustomizePagedView.CustomizationType.ShortcutCustomization);
+ }
+
+ final float alpha = mCustomizePagedView.getAlpha();
+ ValueAnimator alphaAnim = ObjectAnimator.ofFloat(
+ mCustomizePagedView, "alpha", alpha, 1.0f);
+ alphaAnim.setDuration(duration);
+ alphaAnim.start();
+ }
+ });
+ alphaAnim.start();
+ }
+ });
+ }
setupViews();
registerContentObservers();
@@ -308,6 +400,7 @@ public final class Launcher extends Activity
final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
new Thread("WriteLocaleConfiguration") {
+ @Override
public void run() {
writeConfiguration(Launcher.this, localeConfiguration);
}
@@ -383,11 +476,12 @@ public final class Launcher extends Activity
WallpaperManager wpm = (WallpaperManager)getSystemService(WALLPAPER_SERVICE);
Display display = getWindowManager().getDefaultDisplay();
- boolean isPortrait = display.getWidth() < display.getHeight();
-
- final int width = isPortrait ? display.getWidth() : display.getHeight();
- final int height = isPortrait ? display.getHeight() : display.getWidth();
- wpm.suggestDesiredDimensions(width * WALLPAPER_SCREENS_SPAN, height);
+ // TODO: Put back when we decide about scrolling the wallpaper
+ // boolean isPortrait = display.getWidth() < display.getHeight();
+ // final int width = isPortrait ? display.getWidth() : display.getHeight();
+ // final int height = isPortrait ? display.getHeight() : display.getWidth();
+ wpm.suggestDesiredDimensions(Math.max(display.getWidth(), display.getHeight()),
+ Math.max(display.getWidth(), display.getHeight()));
}
// Note: This doesn't do all the client-id magic that BrowserProvider does
@@ -446,7 +540,7 @@ public final class Launcher extends Activity
// note: if the user launches this without a default set, she
// will always be taken to the default URL above; this is
// unavoidable as we must specify a valid URL in order for the
- // chooser to appear, and once the user selects something, that
+ // chooser to appear, and once the user selects something, that
// URL is unavoidably sent to the chosen app.
} else {
try {
@@ -456,7 +550,7 @@ public final class Launcher extends Activity
// bogus; leave intent=null
}
}
-
+
if (intent == null) {
mHotseats[i] = null;
mHotseatLabels[i] = getText(R.string.activity_not_found);
@@ -464,15 +558,15 @@ public final class Launcher extends Activity
}
if (LOGD) {
- Log.d(TAG, "loadHotseats: hotseat " + i
- + " initial intent=["
+ Log.d(TAG, "loadHotseats: hotseat " + i
+ + " initial intent=["
+ intent.toUri(Intent.URI_INTENT_SCHEME)
+ "]");
}
ResolveInfo bestMatch = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
List<ResolveInfo> allMatches = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
- if (LOGD) {
+ if (LOGD) {
Log.d(TAG, "Best match for intent: " + bestMatch);
Log.d(TAG, "All matches: ");
for (ResolveInfo ri : allMatches) {
@@ -481,8 +575,8 @@ public final class Launcher extends Activity
}
// did this resolve to a single app, or the resolver?
if (allMatches.size() == 0 || bestMatch == null) {
- // can't find any activity to handle this. let's leave the
- // intent as-is and let Launcher show a toast when it fails
+ // can't find any activity to handle this. let's leave the
+ // intent as-is and let Launcher show a toast when it fails
// to launch.
mHotseats[i] = intent;
@@ -498,7 +592,7 @@ public final class Launcher extends Activity
break;
}
}
-
+
if (!found) {
if (LOGD) Log.d(TAG, "Multiple options, no default yet");
// the bestMatch is probably the ResolveActivity, meaning the
@@ -523,8 +617,8 @@ public final class Launcher extends Activity
}
if (LOGD) {
- Log.d(TAG, "loadHotseats: hotseat " + i
- + " final intent=["
+ Log.d(TAG, "loadHotseats: hotseat " + i
+ + " final intent=["
+ ((mHotseats[i] == null)
? "null"
: mHotseats[i].toUri(Intent.URI_INTENT_SCHEME))
@@ -545,28 +639,30 @@ public final class Launcher extends Activity
// For example, the user would PICK_SHORTCUT for "Music playlist", and we
// launch over to the Music app to actually CREATE_SHORTCUT.
- if (resultCode == RESULT_OK && mAddItemCellInfo != null) {
+ if (resultCode == RESULT_OK && mAddScreen != -1) {
switch (requestCode) {
case REQUEST_PICK_APPLICATION:
- completeAddApplication(this, data, mAddItemCellInfo);
+ completeAddApplication(
+ this, data, mAddScreen, mAddIntersectCellX, mAddIntersectCellY);
break;
case REQUEST_PICK_SHORTCUT:
processShortcut(data);
break;
case REQUEST_CREATE_SHORTCUT:
- completeAddShortcut(data, mAddItemCellInfo);
+ completeAddShortcut(data, mAddScreen, mAddIntersectCellX, mAddIntersectCellY);
break;
case REQUEST_PICK_LIVE_FOLDER:
addLiveFolder(data);
break;
case REQUEST_CREATE_LIVE_FOLDER:
- completeAddLiveFolder(data, mAddItemCellInfo);
+ completeAddLiveFolder(data, mAddScreen, mAddIntersectCellX, mAddIntersectCellY);
break;
case REQUEST_PICK_APPWIDGET:
- addAppWidget(data);
+ addAppWidgetFromPick(data);
break;
case REQUEST_CREATE_APPWIDGET:
- completeAddAppWidget(data, mAddItemCellInfo);
+ int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+ completeAddAppWidget(appWidgetId, mAddScreen);
break;
case REQUEST_PICK_WALLPAPER:
// We just wanted the activity result here so we can clear mWaitingForResult
@@ -593,14 +689,22 @@ public final class Launcher extends Activity
mRestoring = false;
mOnResumeNeedsLoad = false;
}
+ // When we resume Launcher, a different Activity might be responsible for the app
+ // market intent, so refresh the icon
+ updateAppMarketIcon();
}
@Override
protected void onPause() {
super.onPause();
+ // Some launcher layouts don't have a previous and next view
+ if (mPreviousView != null) {
+ dismissPreview(mPreviousView);
+ }
+ if (mNextView != null) {
+ dismissPreview(mNextView);
+ }
mPaused = true;
- dismissPreview(mPreviousView);
- dismissPreview(mNextView);
mDragController.cancelDrag();
}
@@ -675,6 +779,22 @@ public final class Launcher extends Activity
}
/**
+ * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
+ * State
+ */
+ private static State intToState(int stateOrdinal) {
+ State state = State.WORKSPACE;
+ final State[] stateValues = State.values();
+ for (int i = 0; i < stateValues.length; i++) {
+ if (stateValues[i].ordinal() == stateOrdinal) {
+ state = stateValues[i];
+ break;
+ }
+ }
+ return state;
+ }
+
+ /**
* Restores the previous state, if it exists.
*
* @param savedState The previous state.
@@ -684,30 +804,25 @@ public final class Launcher extends Activity
return;
}
- final boolean allApps = savedState.getBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, false);
- if (allApps) {
+ State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
+
+ if (state == State.ALL_APPS) {
showAllApps(false);
+ } else if (state == State.CUSTOMIZE) {
+ showCustomizationDrawer(false);
}
final int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
if (currentScreen > -1) {
- mWorkspace.setCurrentScreen(currentScreen);
+ mWorkspace.setCurrentPage(currentScreen);
}
final int addScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
+
if (addScreen > -1) {
- mAddItemCellInfo = new CellLayout.CellInfo();
- final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo;
- addItemCellInfo.valid = true;
- addItemCellInfo.screen = addScreen;
- addItemCellInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
- addItemCellInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
- addItemCellInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
- addItemCellInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
- addItemCellInfo.findVacantCellsFromOccupied(
- savedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS),
- savedState.getInt(RUNTIME_STATE_PENDING_ADD_COUNT_X),
- savedState.getInt(RUNTIME_STATE_PENDING_ADD_COUNT_Y));
+ mAddScreen = addScreen;
+ mAddIntersectCellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
+ mAddIntersectCellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
mRestoring = true;
}
@@ -723,7 +838,7 @@ public final class Launcher extends Activity
* Finds all the views we need and configure them properly.
*/
private void setupViews() {
- DragController dragController = mDragController;
+ final DragController dragController = mDragController;
DragLayer dragLayer = (DragLayer) findViewById(R.id.drag_layer);
dragLayer.setDragController(dragController);
@@ -732,8 +847,15 @@ public final class Launcher extends Activity
mAllAppsGrid.setLauncher(this);
mAllAppsGrid.setDragController(dragController);
((View) mAllAppsGrid).setWillNotDraw(false); // We don't want a hole punched in our window.
- // Manage focusability manually since this thing is always visible
- ((View) mAllAppsGrid).setFocusable(false);
+ // Manage focusability manually since this thing is always visible (in non-xlarge)
+ ((View) mAllAppsGrid).setFocusable(false);
+
+ if (LauncherApplication.isScreenXLarge()) {
+ // They need to be INVISIBLE initially so that they will be measured in the layout.
+ // Otherwise the animations are messed up when we show them for the first time.
+ ((View) mAllAppsGrid).setVisibility(View.INVISIBLE);
+ mHomeCustomizationDrawer.setVisibility(View.INVISIBLE);
+ }
mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace);
final Workspace workspace = mWorkspace;
@@ -742,29 +864,39 @@ public final class Launcher extends Activity
DeleteZone deleteZone = (DeleteZone) dragLayer.findViewById(R.id.delete_zone);
mDeleteZone = deleteZone;
- mHandleView = (HandleView) findViewById(R.id.all_apps_button);
- mHandleView.setLauncher(this);
- mHandleView.setOnClickListener(this);
- mHandleView.setOnLongClickListener(this);
+ View handleView = findViewById(R.id.all_apps_button);
+ if (handleView != null && handleView instanceof HandleView) {
+ // we don't use handle view in xlarge mode
+ mHandleView = (HandleView)handleView;
+ mHandleView.setLauncher(this);
+ mHandleView.setOnClickListener(this);
+ mHandleView.setOnLongClickListener(this);
+ }
- ImageView hotseatLeft = (ImageView) findViewById(R.id.hotseat_left);
- hotseatLeft.setContentDescription(mHotseatLabels[0]);
- hotseatLeft.setImageDrawable(mHotseatIcons[0]);
- ImageView hotseatRight = (ImageView) findViewById(R.id.hotseat_right);
- hotseatRight.setContentDescription(mHotseatLabels[1]);
- hotseatRight.setImageDrawable(mHotseatIcons[1]);
+ if (mCustomizePagedView != null) {
+ mCustomizePagedView.setLauncher(this);
+ mCustomizePagedView.setDragController(dragController);
+ mCustomizePagedView.update();
+ } else {
+ ImageView hotseatLeft = (ImageView) findViewById(R.id.hotseat_left);
+ hotseatLeft.setContentDescription(mHotseatLabels[0]);
+ hotseatLeft.setImageDrawable(mHotseatIcons[0]);
+ ImageView hotseatRight = (ImageView) findViewById(R.id.hotseat_right);
+ hotseatRight.setContentDescription(mHotseatLabels[1]);
+ hotseatRight.setImageDrawable(mHotseatIcons[1]);
- mPreviousView = (ImageView) dragLayer.findViewById(R.id.previous_screen);
- mNextView = (ImageView) dragLayer.findViewById(R.id.next_screen);
+ mPreviousView = (ImageView) dragLayer.findViewById(R.id.previous_screen);
+ mNextView = (ImageView) dragLayer.findViewById(R.id.next_screen);
- Drawable previous = mPreviousView.getDrawable();
- Drawable next = mNextView.getDrawable();
- mWorkspace.setIndicators(previous, next);
+ Drawable previous = mPreviousView.getDrawable();
+ Drawable next = mNextView.getDrawable();
+ mWorkspace.setIndicators(previous, next);
- mPreviousView.setHapticFeedbackEnabled(false);
- mPreviousView.setOnLongClickListener(this);
- mNextView.setHapticFeedbackEnabled(false);
- mNextView.setOnLongClickListener(this);
+ mPreviousView.setHapticFeedbackEnabled(false);
+ mPreviousView.setOnLongClickListener(this);
+ mNextView.setHapticFeedbackEnabled(false);
+ mNextView.setOnLongClickListener(this);
+ }
workspace.setOnLongClickListener(this);
workspace.setDragController(dragController);
@@ -772,35 +904,52 @@ public final class Launcher extends Activity
deleteZone.setLauncher(this);
deleteZone.setDragController(dragController);
- deleteZone.setHandle(findViewById(R.id.all_apps_button_cluster));
+ int deleteZoneHandleId;
+ if (LauncherApplication.isScreenXLarge()) {
+ deleteZoneHandleId = R.id.all_apps_button;
+ } else {
+ deleteZoneHandleId = R.id.all_apps_button_cluster;
+ }
+ deleteZone.setHandle(findViewById(deleteZoneHandleId));
+ dragController.addDragListener(deleteZone);
+
+ ApplicationInfoDropTarget infoButton = (ApplicationInfoDropTarget)findViewById(R.id.info_button);
+ if (infoButton != null) {
+ infoButton.setLauncher(this);
+ infoButton.setHandle(findViewById(R.id.configure_button));
+ infoButton.setDragColor(getResources().getColor(R.color.app_info_filter));
+ dragController.addDragListener(infoButton);
+ }
dragController.setDragScoller(workspace);
- dragController.setDragListener(deleteZone);
dragController.setScrollView(dragLayer);
dragController.setMoveTarget(workspace);
// The order here is bottom to top.
dragController.addDropTarget(workspace);
dragController.addDropTarget(deleteZone);
+ if (infoButton != null) {
+ dragController.addDropTarget(infoButton);
+ }
}
@SuppressWarnings({"UnusedDeclaration"})
public void previousScreen(View v) {
- if (!isAllAppsVisible()) {
+ if (mState != State.ALL_APPS) {
mWorkspace.scrollLeft();
}
}
@SuppressWarnings({"UnusedDeclaration"})
public void nextScreen(View v) {
- if (!isAllAppsVisible()) {
+ if (mState != State.ALL_APPS) {
mWorkspace.scrollRight();
}
}
@SuppressWarnings({"UnusedDeclaration"})
public void launchHotSeat(View v) {
- if (isAllAppsVisible()) return;
+ if (mState == State.ALL_APPS) return;
int index = -1;
if (v.getId() == R.id.hotseat_left) {
@@ -819,7 +968,7 @@ public final class Launcher extends Activity
);
}
}
-
+
/**
* Creates a view representing a shortcut.
*
@@ -829,7 +978,7 @@ public final class Launcher extends Activity
*/
View createShortcut(ShortcutInfo info) {
return createShortcut(R.layout.application,
- (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info);
+ (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
}
/**
@@ -844,8 +993,10 @@ public final class Launcher extends Activity
View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
TextView favorite = (TextView) mInflater.inflate(layoutResId, parent, false);
+ Bitmap b = info.getIcon(mIconCache);
+
favorite.setCompoundDrawablesWithIntrinsicBounds(null,
- new FastBitmapDrawable(info.getIcon(mIconCache)),
+ new FastBitmapDrawable(b),
null, null);
favorite.setText(info.title);
favorite.setTag(info);
@@ -860,9 +1011,15 @@ public final class Launcher extends Activity
* @param data The intent describing the application.
* @param cellInfo The position on screen where to create the shortcut.
*/
- void completeAddApplication(Context context, Intent data, CellLayout.CellInfo cellInfo) {
- cellInfo.screen = mWorkspace.getCurrentScreen();
- if (!findSingleSlot(cellInfo)) return;
+ void completeAddApplication(Context context, Intent data, int screen,
+ int intersectCellX, int intersectCellY) {
+ final int[] cellXY = mTmpAddItemCellCoordinates;
+ final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen);
+
+ if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) {
+ showOutOfSpaceMessage();
+ return;
+ }
final ShortcutInfo info = mModel.getShortcutInfo(context.getPackageManager(),
data, context);
@@ -871,7 +1028,8 @@ public final class Launcher extends Activity
info.setActivity(data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
info.container = ItemInfo.NO_ID;
- mWorkspace.addApplicationShortcut(info, cellInfo, isWorkspaceLocked());
+ mWorkspace.addApplicationShortcut(info, screen, cellXY[0], cellXY[1],
+ isWorkspaceLocked(), mAddIntersectCellX, mAddIntersectCellY);
} else {
Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data);
}
@@ -883,16 +1041,22 @@ public final class Launcher extends Activity
* @param data The intent describing the shortcut.
* @param cellInfo The position on screen where to create the shortcut.
*/
- private void completeAddShortcut(Intent data, CellLayout.CellInfo cellInfo) {
- cellInfo.screen = mWorkspace.getCurrentScreen();
- if (!findSingleSlot(cellInfo)) return;
+ private void completeAddShortcut(Intent data, int screen,
+ int intersectCellX, int intersectCellY) {
+ final int[] cellXY = mTmpAddItemCellCoordinates;
+ final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen);
- final ShortcutInfo info = mModel.addShortcut(this, data, cellInfo, false);
+ if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) {
+ showOutOfSpaceMessage();
+ return;
+ }
+
+ final ShortcutInfo info = mModel.addShortcut(
+ this, data, screen, cellXY[0], cellXY[1], false);
if (!mRestoring) {
final View view = createShortcut(info);
- mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1,
- isWorkspaceLocked());
+ mWorkspace.addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, isWorkspaceLocked());
}
}
@@ -900,36 +1064,63 @@ public final class Launcher extends Activity
/**
* Add a widget to the workspace.
*
- * @param data The intent describing the appWidgetId.
+ * @param appWidgetId The app widget id
* @param cellInfo The position on screen where to create the widget.
*/
- private void completeAddAppWidget(Intent data, CellLayout.CellInfo cellInfo) {
- Bundle extras = data.getExtras();
- int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
-
- if (LOGD) Log.d(TAG, "dumping extras content=" + extras.toString());
-
+ private void completeAddAppWidget(int appWidgetId, int screen) {
AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
// Calculate the grid spans needed to fit this widget
- CellLayout layout = (CellLayout) mWorkspace.getChildAt(cellInfo.screen);
- int[] spans = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight);
+ CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen);
+ int[] spanXY = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight, null);
// Try finding open space on Launcher screen
- final int[] xy = mCellCoordinates;
- if (!findSlot(cellInfo, xy, spans[0], spans[1])) {
+ // We have saved the position to which the widget was dragged-- this really only matters
+ // if we are placing widgets on a "spring-loaded" screen
+ final int[] cellXY = mTmpAddItemCellCoordinates;
+
+ // For now, we don't save the coordinate where we dropped the icon because we're not
+ // supporting spring-loaded mini-screens; however, leaving the ability to directly place
+ // a widget on the home screen in case we want to add it in the future
+ int[] touchXY = null;
+ if (mAddDropPosition[0] > -1 && mAddDropPosition[1] > -1) {
+ touchXY = mAddDropPosition;
+ }
+ boolean findNearestVacantAreaFailed = false;
+ boolean foundCellSpan = false;
+ if (touchXY != null) {
+ // when dragging and dropping, just find the closest free spot
+ CellLayout screenLayout = (CellLayout) mWorkspace.getChildAt(screen);
+ int[] result = screenLayout.findNearestVacantArea(
+ touchXY[0], touchXY[1], spanXY[0], spanXY[1], cellXY);
+ findNearestVacantAreaFailed = (result == null);
+ foundCellSpan = !findNearestVacantAreaFailed;
+ } else {
+ if (mAddIntersectCellX != -1 && mAddIntersectCellY != -1) {
+ // if we long pressed on an empty cell to bring up a menu,
+ // make sure we intersect the empty cell
+ foundCellSpan = layout.findCellForSpanThatIntersects(cellXY, spanXY[0], spanXY[1],
+ mAddIntersectCellX, mAddIntersectCellY);
+ } else {
+ // if we went through the menu -> add, just find any spot
+ foundCellSpan = layout.findCellForSpan(cellXY, spanXY[0], spanXY[1]);
+ }
+ }
+
+ if (!foundCellSpan) {
if (appWidgetId != -1) mAppWidgetHost.deleteAppWidgetId(appWidgetId);
+ showOutOfSpaceMessage();
return;
}
// Build Launcher-specific widget info and save to database
LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId);
- launcherInfo.spanX = spans[0];
- launcherInfo.spanY = spans[1];
+ launcherInfo.spanX = spanXY[0];
+ launcherInfo.spanY = spanXY[1];
LauncherModel.addItemToDatabase(this, launcherInfo,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
- mWorkspace.getCurrentScreen(), xy[0], xy[1], false);
+ screen, cellXY[0], cellXY[1], false);
if (!mRestoring) {
mDesktopItems.add(launcherInfo);
@@ -940,11 +1131,15 @@ public final class Launcher extends Activity
launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
launcherInfo.hostView.setTag(launcherInfo);
- mWorkspace.addInCurrentScreen(launcherInfo.hostView, xy[0], xy[1],
+ mWorkspace.addInScreen(launcherInfo.hostView, screen, cellXY[0], cellXY[1],
launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
}
}
+ void showOutOfSpaceMessage() {
+ Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
+ }
+
public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
mDesktopItems.remove(launcherInfo);
launcherInfo.hostView = null;
@@ -954,6 +1149,10 @@ public final class Launcher extends Activity
return mAppWidgetHost;
}
+ public LauncherModel getModel() {
+ return mModel;
+ }
+
void closeSystemDialogs() {
getWindow().closeAllPanels();
@@ -986,11 +1185,17 @@ public final class Launcher extends Activity
boolean alreadyOnHome = ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
!= Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
- boolean allAppsVisible = isAllAppsVisible();
- if (!mWorkspace.isDefaultScreenShowing()) {
- mWorkspace.moveToDefaultScreen(alreadyOnHome && !allAppsVisible);
+
+ // in all these cases, only animate if we're already on home
+ if (LauncherApplication.isScreenXLarge()) {
+ mWorkspace.unshrink(alreadyOnHome);
}
- closeAllApps(alreadyOnHome && allAppsVisible);
+ if (!mWorkspace.isDefaultPageShowing()) {
+ // on the phone, we don't animate the change to the workspace if all apps is visible
+ mWorkspace.moveToDefaultScreen(alreadyOnHome &&
+ (LauncherApplication.isScreenXLarge() || mState != State.ALL_APPS));
+ }
+ showWorkspace(alreadyOnHome);
final View v = getWindow().peekDecorView();
if (v != null && v.getWindowToken() != null) {
@@ -1005,11 +1210,18 @@ public final class Launcher extends Activity
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// Do not call super here
mSavedInstanceState = savedInstanceState;
+
+ if (mHomeCustomizationDrawer != null) {
+ String cur = savedInstanceState.getString("currentTab");
+ if (cur != null) {
+ mHomeCustomizationDrawer.setCurrentTabByTag(cur);
+ }
+ }
}
@Override
protected void onSaveInstanceState(Bundle outState) {
- outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentScreen());
+ outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPage());
final ArrayList<Folder> folders = mWorkspace.getOpenFolders();
if (folders.size() > 0) {
@@ -1024,30 +1236,25 @@ public final class Launcher extends Activity
super.onSaveInstanceState(outState);
}
- // TODO should not do this if the drawer is currently closing.
- if (isAllAppsVisible()) {
- outState.putBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, true);
- }
-
- if (mAddItemCellInfo != null && mAddItemCellInfo.valid && mWaitingForResult) {
- final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo;
- final CellLayout layout = (CellLayout) mWorkspace.getChildAt(addItemCellInfo.screen);
+ outState.putInt(RUNTIME_STATE, mState.ordinal());
- outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, addItemCellInfo.screen);
- outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, addItemCellInfo.cellX);
- outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, addItemCellInfo.cellY);
- outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, addItemCellInfo.spanX);
- outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, addItemCellInfo.spanY);
- outState.putInt(RUNTIME_STATE_PENDING_ADD_COUNT_X, layout.getCountX());
- outState.putInt(RUNTIME_STATE_PENDING_ADD_COUNT_Y, layout.getCountY());
- outState.putBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS,
- layout.getOccupiedCells());
+ if (mAddScreen > -1 && mWaitingForResult) {
+ outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, mAddScreen);
+ outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mAddIntersectCellX);
+ outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mAddIntersectCellY);
}
if (mFolderInfo != null && mWaitingForResult) {
outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
}
+
+ if (mHomeCustomizationDrawer != null) {
+ String currentTabTag = mHomeCustomizationDrawer.getCurrentTabTag();
+ if (currentTabTag != null) {
+ outState.putString("currentTab", currentTabTag);
+ }
+ }
}
@Override
@@ -1067,9 +1274,14 @@ public final class Launcher extends Activity
unbindDesktopItems();
getContentResolver().unregisterContentObserver(mWidgetObserver);
-
- dismissPreview(mPreviousView);
- dismissPreview(mNextView);
+
+ // Some launcher layouts don't have a previous and next view
+ if (mPreviousView != null) {
+ dismissPreview(mPreviousView);
+ }
+ if (mNextView != null) {
+ dismissPreview(mNextView);
+ }
unregisterReceiver(mCloseSystemDialogsReceiver);
}
@@ -1084,7 +1296,7 @@ public final class Launcher extends Activity
public void startSearch(String initialQuery, boolean selectInitialQuery,
Bundle appSearchData, boolean globalSearch) {
- closeAllApps(true);
+ showWorkspace(true);
if (initialQuery == null) {
// Use any text typed in the launcher as the initial query
@@ -1143,19 +1355,19 @@ public final class Launcher extends Activity
// If all apps is animating, don't show the menu, because we don't know
// which one to show.
- if (mAllAppsGrid.isVisible() && !mAllAppsGrid.isOpaque()) {
+ if (mAllAppsGrid.isAnimating()) {
return false;
}
// Only show the add and wallpaper options when we're not in all apps.
- boolean visible = !mAllAppsGrid.isOpaque();
+ boolean visible = !mAllAppsGrid.isVisible();
menu.setGroupVisible(MENU_GROUP_ADD, visible);
menu.setGroupVisible(MENU_GROUP_WALLPAPER, visible);
// Disable add if the workspace is full.
if (visible) {
- mMenuAddInfo = mWorkspace.findAllVacantCells(null);
- menu.setGroupEnabled(MENU_GROUP_ADD, mMenuAddInfo != null && mMenuAddInfo.valid);
+ CellLayout layout = (CellLayout) mWorkspace.getChildAt(mWorkspace.getCurrentPage());
+ menu.setGroupEnabled(MENU_GROUP_ADD, layout.existsEmptyCell());
}
return true;
@@ -1200,17 +1412,49 @@ public final class Launcher extends Activity
}
private void addItems() {
- closeAllApps(true);
- showAddDialog(mMenuAddInfo);
+ if (LauncherApplication.isScreenXLarge()) {
+ // Animate the widget chooser up from the bottom of the screen
+ if (mState != State.CUSTOMIZE) {
+ showCustomizationDrawer(true);
+ }
+ } else {
+ showWorkspace(true);
+ showAddDialog(-1, -1);
+ }
+ }
+
+ private void resetAddInfo() {
+ mAddScreen = -1;
+ mAddIntersectCellX = -1;
+ mAddIntersectCellY = -1;
+ mAddDropPosition = null;
+ }
+
+ void addAppWidgetFromDrop(PendingAddWidgetInfo info, int screen, int[] position) {
+ resetAddInfo();
+ mAddScreen = screen;
+
+ // only set mAddDropPosition if we dropped on home screen in "spring-loaded" manner
+ mAddDropPosition = position;
+
+ int appWidgetId = getAppWidgetHost().allocateAppWidgetId();
+ AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, info.componentName);
+ addAppWidgetImpl(appWidgetId, info);
}
private void manageApps() {
startActivity(new Intent(android.provider.Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS));
}
- void addAppWidget(Intent data) {
+ void addAppWidgetFromPick(Intent data) {
// TODO: catch bad widget exception when sent
int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+ // TODO: Is this log message meaningful?
+ if (LOGD) Log.d(TAG, "dumping extras content=" + data.getExtras());
+ addAppWidgetImpl(appWidgetId, null);
+ }
+
+ void addAppWidgetImpl(int appWidgetId, PendingAddWidgetInfo info) {
AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
if (appWidget.configure != null) {
@@ -1218,14 +1462,53 @@ public final class Launcher extends Activity
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
intent.setComponent(appWidget.configure);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ if (info != null) {
+ if (info.mimeType != null && !info.mimeType.isEmpty()) {
+ intent.putExtra(
+ InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE,
+ info.mimeType);
+
+ final String mimeType = info.mimeType;
+ final ClipData clipData = (ClipData) info.configurationData;
+ final ClipDescription clipDesc = clipData.getDescription();
+ for (int i = 0; i < clipDesc.getMimeTypeCount(); ++i) {
+ if (clipDesc.getMimeType(i).equals(mimeType)) {
+ final ClipData.Item item = clipData.getItem(i);
+ final CharSequence stringData = item.getText();
+ final Uri uriData = item.getUri();
+ final Intent intentData = item.getIntent();
+ final String key =
+ InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA;
+ if (uriData != null) {
+ intent.putExtra(key, uriData);
+ } else if (intentData != null) {
+ intent.putExtra(key, intentData);
+ } else if (stringData != null) {
+ intent.putExtra(key, stringData);
+ }
+ break;
+ }
+ }
+ }
+ }
startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
} else {
// Otherwise just add it
- onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data);
+ completeAddAppWidget(appWidgetId, mAddScreen);
}
}
+ void processShortcutFromDrop(ComponentName componentName, int screen, int[] position) {
+ resetAddInfo();
+ mAddScreen = screen;
+ mAddDropPosition = position;
+
+ Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+ createShortcutIntent.setComponent(componentName);
+ processShortcut(createShortcutIntent);
+ }
+
void processShortcut(Intent intent) {
// Handle case where user selected "Applications"
String applicationName = getResources().getString(R.string.group_applications);
@@ -1244,59 +1527,81 @@ public final class Launcher extends Activity
}
}
- void addLiveFolder(Intent intent) {
+ void processWallpaper(Intent intent) {
+ startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
+ }
+
+ void addLiveFolderFromDrop(ComponentName componentName, int screen, int[] position) {
+ resetAddInfo();
+ mAddScreen = screen;
+ mAddDropPosition = position;
+
+ Intent createFolderIntent = new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER);
+ createFolderIntent.setComponent(componentName);
+
+ addLiveFolder(createFolderIntent);
+ }
+
+ void addLiveFolder(Intent intent) { // YYY add screen intersect etc. parameters here
// Handle case where user selected "Folder"
String folderName = getResources().getString(R.string.group_folder);
String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
if (folderName != null && folderName.equals(shortcutName)) {
- addFolder();
+ addFolder(mAddScreen, mAddIntersectCellX, mAddIntersectCellY);
} else {
startActivityForResultSafely(intent, REQUEST_CREATE_LIVE_FOLDER);
}
}
- void addFolder() {
+ void addFolder(int screen, int intersectCellX, int intersectCellY) {
UserFolderInfo folderInfo = new UserFolderInfo();
folderInfo.title = getText(R.string.folder_name);
- CellLayout.CellInfo cellInfo = mAddItemCellInfo;
- cellInfo.screen = mWorkspace.getCurrentScreen();
- if (!findSingleSlot(cellInfo)) return;
+ final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen);
+ final int[] cellXY = mTmpAddItemCellCoordinates;
+ if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) {
+ showOutOfSpaceMessage();
+ return;
+ }
// Update the model
LauncherModel.addItemToDatabase(this, folderInfo,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
- mWorkspace.getCurrentScreen(), cellInfo.cellX, cellInfo.cellY, false);
+ screen, cellXY[0], cellXY[1], false);
sFolders.put(folderInfo.id, folderInfo);
// Create the view
FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
- (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), folderInfo);
- mWorkspace.addInCurrentScreen(newFolder,
- cellInfo.cellX, cellInfo.cellY, 1, 1, isWorkspaceLocked());
+ (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()),
+ folderInfo, mIconCache);
+ mWorkspace.addInScreen(newFolder, screen, cellXY[0], cellXY[1], 1, 1, isWorkspaceLocked());
}
void removeFolder(FolderInfo folder) {
sFolders.remove(folder.id);
}
- private void completeAddLiveFolder(Intent data, CellLayout.CellInfo cellInfo) {
- cellInfo.screen = mWorkspace.getCurrentScreen();
- if (!findSingleSlot(cellInfo)) return;
+ private void completeAddLiveFolder(
+ Intent data, int screen, int intersectCellX, int intersectCellY) {
+ final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen);
+ final int[] cellXY = mTmpAddItemCellCoordinates;
+ if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) {
+ showOutOfSpaceMessage();
+ return;
+ }
- final LiveFolderInfo info = addLiveFolder(this, data, cellInfo, false);
+ final LiveFolderInfo info = addLiveFolder(this, data, screen, cellXY[0], cellXY[1], false);
if (!mRestoring) {
final View view = LiveFolderIcon.fromXml(R.layout.live_folder_icon, this,
- (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info);
- mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1,
- isWorkspaceLocked());
+ (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
+ mWorkspace.addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, isWorkspaceLocked());
}
}
static LiveFolderInfo addLiveFolder(Context context, Intent data,
- CellLayout.CellInfo cellInfo, boolean notify) {
+ int screen, int cellX, int cellY, boolean notify) {
Intent baseIntent = data.getParcelableExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT);
String name = data.getStringExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME);
@@ -1332,35 +1637,12 @@ public final class Launcher extends Activity
LiveFolders.DISPLAY_MODE_GRID);
LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
- cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
+ screen, cellX, cellY, notify);
sFolders.put(info.id, info);
return info;
}
- private boolean findSingleSlot(CellLayout.CellInfo cellInfo) {
- final int[] xy = new int[2];
- if (findSlot(cellInfo, xy, 1, 1)) {
- cellInfo.cellX = xy[0];
- cellInfo.cellY = xy[1];
- return true;
- }
- return false;
- }
-
- private boolean findSlot(CellLayout.CellInfo cellInfo, int[] xy, int spanX, int spanY) {
- if (!cellInfo.findCellForSpan(xy, spanX, spanY)) {
- boolean[] occupied = mSavedState != null ?
- mSavedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS) : null;
- cellInfo = mWorkspace.findAllVacantCells(occupied);
- if (!cellInfo.findCellForSpan(xy, spanX, spanY)) {
- Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
- return false;
- }
- }
- return true;
- }
-
private void showNotifications() {
final StatusBarManager statusBar = (StatusBarManager) getSystemService(STATUS_BAR_SERVICE);
if (statusBar != null) {
@@ -1369,7 +1651,7 @@ public final class Launcher extends Activity
}
private void startWallpaper() {
- closeAllApps(true);
+ showWorkspace(true);
final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
Intent chooser = Intent.createChooser(pickWallpaper,
getText(R.string.chooser_wallpaper));
@@ -1422,13 +1704,16 @@ public final class Launcher extends Activity
@Override
public void onBackPressed() {
- if (isAllAppsVisible()) {
- closeAllApps(true);
+ if (mState == State.ALL_APPS || mState == State.CUSTOMIZE) {
+ showWorkspace(true);
} else {
closeFolder();
}
- dismissPreview(mPreviousView);
- dismissPreview(mNextView);
+ // Some launcher layouts don't have a previous and next view
+ if (mPreviousView != null) {
+ dismissPreview(mPreviousView);
+ dismissPreview(mNextView);
+ }
}
private void closeFolder() {
@@ -1442,7 +1727,8 @@ public final class Launcher extends Activity
folder.getInfo().opened = false;
ViewGroup parent = (ViewGroup) folder.getParent();
if (parent != null) {
- parent.removeView(folder);
+ CellLayout cl = (CellLayout) parent;
+ cl.removeViewWithoutMarkingCells(folder);
if (folder instanceof DropTarget) {
// Live folders aren't DropTargets.
mDragController.removeDropTarget((DropTarget)folder);
@@ -1486,14 +1772,78 @@ public final class Launcher extends Activity
} else if (tag instanceof FolderInfo) {
handleFolderClick((FolderInfo) tag);
} else if (v == mHandleView) {
- if (isAllAppsVisible()) {
- closeAllApps(true);
+ if (mState == State.ALL_APPS) {
+ showWorkspace(true);
} else {
showAllApps(true);
}
}
}
+ public boolean onTouch(View v, MotionEvent event) {
+ // this is an intercepted event being forwarded from mWorkspace;
+ // clicking anywhere on the workspace causes the customization drawer to slide down
+ showWorkspace(true);
+ return false;
+ }
+
+ /**
+ * Event handler for the search button
+ *
+ * @param v The view that was clicked.
+ */
+ public void onClickSearchButton(View v) {
+ startSearch(null, false, null, true);
+ }
+
+ /**
+ * Event handler for the "gear" button that appears on the home screen, which
+ * enters home screen customization mode.
+ *
+ * @param v The view that was clicked.
+ */
+ public void onClickConfigureButton(View v) {
+ addItems();
+ }
+
+ /**
+ * Event handler for the "grid" button that appears on the home screen, which
+ * enters all apps mode.
+ *
+ * @param v The view that was clicked.
+ */
+ public void onClickAllAppsButton(View v) {
+ showAllApps(true);
+ }
+
+ public void onClickAppMarketButton(View v) {
+ if (mAppMarketIntent != null) {
+ startActivitySafely(mAppMarketIntent, "app market");
+ }
+ }
+
+ void startApplicationDetailsActivity(ComponentName componentName) {
+ String packageName = componentName.getPackageName();
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+ Uri.fromParts("package", packageName, null));
+ startActivity(intent);
+ }
+
+ void startApplicationUninstallActivity(ApplicationInfo appInfo) {
+ if ((appInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) == 0) {
+ // System applications cannot be installed. For now, show a toast explaining that.
+ // We may give them the option of disabling apps this way.
+ int messageId = R.string.uninstall_system_app_text;
+ Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
+ } else {
+ String packageName = appInfo.componentName.getPackageName();
+ String className = appInfo.componentName.getClassName();
+ Intent intent = new Intent(
+ Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
+ startActivity(intent);
+ }
+ }
+
void startActivitySafely(Intent intent, Object tag) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
@@ -1509,7 +1859,7 @@ public final class Launcher extends Activity
+ "tag="+ tag + " intent=" + intent, e);
}
}
-
+
void startActivityForResultSafely(Intent intent, int requestCode) {
try {
startActivityForResult(intent, requestCode);
@@ -1534,10 +1884,10 @@ public final class Launcher extends Activity
Folder openFolder = mWorkspace.getFolderForTag(folderInfo);
int folderScreen;
if (openFolder != null) {
- folderScreen = mWorkspace.getScreenForView(openFolder);
+ folderScreen = mWorkspace.getPageForView(openFolder);
// .. and close it
closeFolder(openFolder);
- if (folderScreen != mWorkspace.getCurrentScreen()) {
+ if (folderScreen != mWorkspace.getCurrentPage()) {
// Close any folder open on the current screen
closeFolder();
// Pull the folder onto this screen
@@ -1548,13 +1898,13 @@ public final class Launcher extends Activity
}
/**
- * Opens the user fodler described by the specified tag. The opening of the folder
+ * Opens the user folder described by the specified tag. The opening of the folder
* is animated relative to the specified View. If the View is null, no animation
* is played.
*
* @param folderInfo The FolderInfo describing the folder to open.
*/
- private void openFolder(FolderInfo folderInfo) {
+ public void openFolder(FolderInfo folderInfo) {
Folder openFolder;
if (folderInfo instanceof UserFolderInfo) {
@@ -1571,28 +1921,29 @@ public final class Launcher extends Activity
openFolder.bind(folderInfo);
folderInfo.opened = true;
- mWorkspace.addInScreen(openFolder, folderInfo.screen, 0, 0, 4, 4);
+ mWorkspace.addInFullScreen(openFolder, folderInfo.screen);
+
openFolder.onOpen();
}
public boolean onLongClick(View v) {
switch (v.getId()) {
case R.id.previous_screen:
- if (!isAllAppsVisible()) {
+ if (mState != State.ALL_APPS) {
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
showPreviews(v);
}
return true;
case R.id.next_screen:
- if (!isAllAppsVisible()) {
+ if (mState != State.ALL_APPS) {
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
showPreviews(v);
}
return true;
case R.id.all_apps_button:
- if (!isAllAppsVisible()) {
+ if (mState != State.ALL_APPS) {
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
showPreviews(v);
@@ -1608,28 +1959,33 @@ public final class Launcher extends Activity
v = (View) v.getParent();
}
- CellLayout.CellInfo cellInfo = (CellLayout.CellInfo) v.getTag();
+ resetAddInfo();
+ CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
// This happens when long clicking an item with the dpad/trackball
- if (cellInfo == null) {
+ if (longClickCellInfo == null || !longClickCellInfo.valid) {
return true;
}
+ final View itemUnderLongClick = longClickCellInfo.cell;
+
if (mWorkspace.allowLongPress()) {
- if (cellInfo.cell == null) {
- if (cellInfo.valid) {
- // User long pressed on empty space
- mWorkspace.setAllowLongPress(false);
- mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
- showAddDialog(cellInfo);
+ if (itemUnderLongClick == null) {
+ // User long pressed on empty space
+ mWorkspace.setAllowLongPress(false);
+ mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ if (!LauncherApplication.isScreenXLarge()) {
+ showAddDialog(longClickCellInfo.cellX, longClickCellInfo.cellY);
}
} else {
- if (!(cellInfo.cell instanceof Folder)) {
+ if (!(itemUnderLongClick instanceof Folder)) {
// User long pressed on an item
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
- mWorkspace.startDrag(cellInfo);
+ mAddIntersectCellX = longClickCellInfo.cellX;
+ mAddIntersectCellY = longClickCellInfo.cellY;
+ mWorkspace.startDrag(longClickCellInfo);
}
}
}
@@ -1669,9 +2025,9 @@ public final class Launcher extends Activity
final Workspace workspace = mWorkspace;
CellLayout cell = ((CellLayout) workspace.getChildAt(start));
-
+
float max = workspace.getChildCount();
-
+
final Rect r = new Rect();
resources.getDrawable(R.drawable.preview_background).getPadding(r);
int extraW = (int) ((r.left + r.right) * max);
@@ -1709,7 +2065,7 @@ public final class Launcher extends Activity
final Canvas c = new Canvas(bitmap);
c.scale(scale, scale);
c.translate(-cell.getLeftPadding(), -cell.getTopPadding());
- cell.dispatchDraw(c);
+ cell.drawChildren(c);
image.setBackgroundDrawable(resources.getDrawable(R.drawable.preview_background));
image.setImageBitmap(bitmap);
@@ -1717,12 +2073,12 @@ public final class Launcher extends Activity
image.setOnClickListener(handler);
image.setOnFocusChangeListener(handler);
image.setFocusable(true);
- if (i == mWorkspace.getCurrentScreen()) image.requestFocus();
+ if (i == mWorkspace.getCurrentPage()) image.requestFocus();
preview.addView(image,
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
- bitmaps.add(bitmap);
+ bitmaps.add(bitmap);
}
final PopupWindow p = new PopupWindow(this);
@@ -1743,7 +2099,7 @@ public final class Launcher extends Activity
anchor.setTag(p);
anchor.setTag(R.id.workspace, preview);
- anchor.setTag(R.id.icon, bitmaps);
+ anchor.setTag(R.id.icon, bitmaps);
}
class PreviewTouchHandler implements View.OnClickListener, Runnable, View.OnFocusChangeListener {
@@ -1754,17 +2110,17 @@ public final class Launcher extends Activity
}
public void onClick(View v) {
- mWorkspace.snapToScreen((Integer) v.getTag());
+ mWorkspace.snapToPage((Integer) v.getTag());
v.post(this);
}
public void run() {
- dismissPreview(mAnchor);
+ dismissPreview(mAnchor);
}
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
- mWorkspace.snapToScreen((Integer) v.getTag());
+ mWorkspace.snapToPage((Integer) v.getTag());
}
}
}
@@ -1807,13 +2163,17 @@ public final class Launcher extends Activity
showDialog(DIALOG_RENAME_FOLDER);
}
- private void showAddDialog(CellLayout.CellInfo cellInfo) {
- mAddItemCellInfo = cellInfo;
+ private void showAddDialog(int intersectX, int intersectY) {
+ resetAddInfo();
+ mAddIntersectCellX = intersectX;
+ mAddIntersectCellY = intersectY;
+ mAddScreen = mWorkspace.getCurrentPage();
mWaitingForResult = true;
showDialog(DIALOG_CREATE_SHORTCUT);
}
private void pickShortcut() {
+ // Insert extra item to handle picking application
Bundle bundle = new Bundle();
ArrayList<String> shortcutNames = new ArrayList<String>();
@@ -1915,24 +2275,370 @@ public final class Launcher extends Activity
// Now a part of LauncherModel.Callbacks. Used to reorder loading steps.
public boolean isAllAppsVisible() {
- return (mAllAppsGrid != null) ? mAllAppsGrid.isVisible() : false;
+ return mState == State.ALL_APPS;
}
// AllAppsView.Watcher
public void zoomed(float zoom) {
- if (zoom == 1.0f) {
+ // In XLarge view, we zoom down the workspace below all apps so it's still visible
+ if (zoom == 1.0f && !LauncherApplication.isScreenXLarge()) {
mWorkspace.setVisibility(View.GONE);
}
}
+
+ private void showToolbarButton(View button) {
+ button.setAlpha(1.0f);
+ button.setVisibility(View.VISIBLE);
+ button.setFocusable(true);
+ button.setClickable(true);
+ }
+
+ private void hideToolbarButton(View button) {
+ button.setAlpha(0.0f);
+ // We can't set it to GONE, otherwise the RelativeLayout gets screwed up
+ button.setVisibility(View.INVISIBLE);
+ button.setFocusable(false);
+ button.setClickable(false);
+ }
+
+ /**
+ * Helper function for showing or hiding a toolbar button, possibly animated.
+ *
+ * @param show If true, create an animation to the show the item. Otherwise, hide it.
+ * @param view The toolbar button to be animated
+ * @param seq A AnimatorSet that will be used to animate the transition. If null, the
+ * transition will not be animated.
+ */
+ private void hideOrShowToolbarButton(boolean show, final View view, AnimatorSet seq) {
+ final boolean showing = show;
+ final boolean hiding = !show;
+
+ final int duration = show ?
+ getResources().getInteger(R.integer.config_toolbarButtonFadeInTime) :
+ getResources().getInteger(R.integer.config_toolbarButtonFadeOutTime);
+
+ if (seq != null) {
+ Animator anim = ObjectAnimator.ofFloat(view, "alpha", show ? 1.0f : 0.0f);
+ anim.setDuration(duration);
+ anim.addListener(new LauncherAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (showing) showToolbarButton(view);
+ }
+ @Override
+ public void onAnimationEndOrCancel(Animator animation) {
+ if (hiding) hideToolbarButton(view);
+ }
+ });
+ seq.play(anim);
+ } else {
+ if (showing) {
+ showToolbarButton(view);
+ } else {
+ hideToolbarButton(view);
+ }
+ }
+ }
+
+ /**
+ * Show/hide the appropriate toolbar buttons for newState.
+ * If showSeq or hideSeq is null, the transition will be done immediately (not animated).
+ *
+ * @param newState The state that is being switched to
+ * @param showSeq AnimatorSet in which to put "show" animations, or null.
+ * @param hideSeq AnimatorSet in which to put "hide" animations, or null.
+ */
+ private void hideAndShowToolbarButtons(State newState, AnimatorSet showSeq, AnimatorSet hideSeq) {
+ final View searchButton = findViewById(R.id.search_button);
+ final View allAppsButton = findViewById(R.id.all_apps_button);
+ final View marketButton = findViewById(R.id.market_button);
+ final View configureButton = findViewById(R.id.configure_button);
+
+ switch (newState) {
+ case WORKSPACE:
+ hideOrShowToolbarButton(true, searchButton, showSeq);
+ hideOrShowToolbarButton(true, allAppsButton, showSeq);
+ hideOrShowToolbarButton(true, configureButton, showSeq);
+ hideOrShowToolbarButton(false, marketButton, hideSeq);
+ mDeleteZone.setHandle(allAppsButton);
+ break;
+ case ALL_APPS:
+ hideOrShowToolbarButton(true, configureButton, showSeq);
+ hideOrShowToolbarButton(true, marketButton, showSeq);
+ hideOrShowToolbarButton(false, searchButton, hideSeq);
+ hideOrShowToolbarButton(false, allAppsButton, hideSeq);
+ mDeleteZone.setHandle(marketButton);
+ break;
+ case CUSTOMIZE:
+ hideOrShowToolbarButton(true, allAppsButton, showSeq);
+ hideOrShowToolbarButton(false, searchButton, hideSeq);
+ hideOrShowToolbarButton(false, marketButton, hideSeq);
+ hideOrShowToolbarButton(false, configureButton, hideSeq);
+ mDeleteZone.setHandle(allAppsButton);
+ break;
+ }
+ }
+
+ /**
+ * Helper method for the cameraZoomIn/cameraZoomOut animations
+ * @param view The view being animated
+ * @param state The state that we are moving in or out of -- either ALL_APPS or CUSTOMIZE
+ * @param scaleFactor The scale factor used for the zoom
+ */
+ private void setPivotsForZoom(View view, State state, float scaleFactor) {
+ final int height = view.getHeight();
+ view.setPivotX(view.getWidth() / 2.0f);
+ // Set pivotY so that at the starting zoom factor, the view is off-screen by a small margin
+ // Assumes that the view is normally anchored to either the top or bottom of the screen
+ final int margin = getResources().getInteger(R.integer.config_allAppsVerticalOffset);
+ if (state == State.ALL_APPS) {
+ view.setPivotY(height + ((view.getTop() + height) / scaleFactor) + margin);
+ } else {
+ view.setPivotY(0.0f - (view.getTop() / scaleFactor) - margin);
+ }
+ }
+
+ /**
+ * Zoom the camera out from the workspace to reveal 'toView'.
+ * Assumes that the view to show is anchored at either the very top or very bottom
+ * of the screen.
+ * @param toState The state to zoom out to. Must be ALL_APPS or CUSTOMIZE.
+ */
+ private void cameraZoomOut(State toState, boolean animated) {
+ final Resources res = getResources();
+ final int duration = res.getInteger(R.integer.config_allAppsZoomInTime);
+ final float scale = (float) res.getInteger(R.integer.config_allAppsZoomScaleFactor);
+ final boolean toAllApps = (toState == State.ALL_APPS);
+ final View toView = toAllApps ? (View) mAllAppsGrid : mHomeCustomizationDrawer;
+
+ setPivotsForZoom(toView, toState, scale);
+
+ if (toAllApps) {
+ mWorkspace.shrinkToBottom(animated);
+ } else {
+ mWorkspace.shrinkToTop(animated);
+ }
+
+ if (animated) {
+ ValueAnimator scaleAnim = ObjectAnimator.ofPropertyValuesHolder(toView,
+ PropertyValuesHolder.ofFloat("scaleX", scale, 1.0f),
+ PropertyValuesHolder.ofFloat("scaleY", scale, 1.0f));
+ scaleAnim.setDuration(duration);
+ scaleAnim.setInterpolator(new DecelerateInterpolator());
+ scaleAnim.addListener(new LauncherAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Prepare the position
+ toView.setTranslationX(0.0f);
+ toView.setTranslationY(0.0f);
+ toView.setVisibility(View.VISIBLE);
+ }
+ @Override
+ public void onAnimationEndOrCancel(Animator animation) {
+ // If we don't set the final scale values here, if this animation is cancelled
+ // it will have the wrong scale value and subsequent cameraPan animations will
+ // not fix that
+ toView.setScaleX(1.0f);
+ toView.setScaleY(1.0f);
+ }
+ });
+
+ AnimatorSet toolbarHideAnim = new AnimatorSet();
+ AnimatorSet toolbarShowAnim = new AnimatorSet();
+ hideAndShowToolbarButtons(toState, toolbarShowAnim, toolbarHideAnim);
+
+ // toView should appear right at the end of the workspace shrink animation
+ final int startDelay = res.getInteger(R.integer.config_workspaceShrinkTime) - duration;
+
+ if (mStateAnimation != null) mStateAnimation.cancel();
+ mStateAnimation = new AnimatorSet();
+ mStateAnimation.playTogether(scaleAnim, toolbarHideAnim);
+ mStateAnimation.play(scaleAnim).after(startDelay);
+
+ // Show the new toolbar buttons just as the main animation is ending
+ final int fadeInTime = res.getInteger(R.integer.config_toolbarButtonFadeInTime);
+ mStateAnimation.play(toolbarShowAnim).after(duration + startDelay - fadeInTime);
+ mStateAnimation.start();
+ } else {
+ toView.setTranslationX(0.0f);
+ toView.setTranslationY(0.0f);
+ toView.setScaleX(1.0f);
+ toView.setScaleY(1.0f);
+ toView.setVisibility(View.VISIBLE);
+ hideAndShowToolbarButtons(toState, null, null);
+ }
+ }
+
+ /**
+ * Zoom the camera back into the workspace, hiding 'fromView'.
+ * This is the opposite of cameraZoomOut.
+ * @param fromState The current state (must be ALL_APPS or CUSTOMIZE).
+ * @param animated If true, the transition will be animated.
+ */
+ private void cameraZoomIn(State fromState, boolean animated) {
+ Resources res = getResources();
+ int duration = res.getInteger(R.integer.config_allAppsZoomOutTime);
+ float scaleFactor = (float) res.getInteger(R.integer.config_allAppsZoomScaleFactor);
+ final View fromView =
+ (fromState == State.ALL_APPS) ? (View) mAllAppsGrid : mHomeCustomizationDrawer;
+
+ mCustomizePagedView.endChoiceMode();
+ mAllAppsPagedView.endChoiceMode();
+
+ setPivotsForZoom(fromView, fromState, scaleFactor);
+
+ mWorkspace.unshrink(animated);
+
+ if (animated) {
+ if (mStateAnimation != null) mStateAnimation.cancel();
+ mStateAnimation = new AnimatorSet();
+ ValueAnimator scaleAnim = ObjectAnimator.ofPropertyValuesHolder(fromView,
+ PropertyValuesHolder.ofFloat("scaleX", scaleFactor),
+ PropertyValuesHolder.ofFloat("scaleY", scaleFactor));
+ scaleAnim.setDuration(duration);
+ scaleAnim.setInterpolator(new AccelerateInterpolator());
+ mStateAnimation.addListener(new LauncherAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEndOrCancel(Animator animation) {
+ fromView.setVisibility(View.GONE);
+ }
+ });
+
+ AnimatorSet toolbarHideAnim = new AnimatorSet();
+ AnimatorSet toolbarShowAnim = new AnimatorSet();
+ hideAndShowToolbarButtons(State.WORKSPACE, toolbarShowAnim, toolbarHideAnim);
+
+ mStateAnimation.playTogether(scaleAnim, toolbarHideAnim);
+
+ // Show the new toolbar buttons at the very end of the whole animation
+ final int fadeInTime = res.getInteger(R.integer.config_toolbarButtonFadeInTime);
+ final int unshrinkTime = res.getInteger(R.integer.config_workspaceUnshrinkTime);
+ mStateAnimation.play(toolbarShowAnim).after(unshrinkTime - fadeInTime);
+ mStateAnimation.start();
+ } else {
+ fromView.setVisibility(View.GONE);
+ hideAndShowToolbarButtons(State.WORKSPACE, null, null);
+ }
+ }
+
+ /**
+ * Pan the camera in the vertical plane between 'fromView' and 'toView'.
+ * This is the transition used on xlarge screens to go between all apps and
+ * the home customization drawer.
+ * @param fromState The view to pan away from. Must be ALL_APPS or CUSTOMIZE.
+ * @param toState The view to pan into the frame. Must be ALL_APPS or CUSTOMIZE.
+ * @param animated If true, the transition will be animated.
+ */
+ private void cameraPan(State fromState, State toState, boolean animated) {
+ final Resources res = getResources();
+ final int duration = res.getInteger(R.integer.config_allAppsCameraPanTime);
+ final int workspaceHeight = mWorkspace.getHeight();
+
+ final boolean fromAllApps = (fromState == State.ALL_APPS);
+ final View fromView = fromAllApps ? (View) mAllAppsGrid : mHomeCustomizationDrawer;
+ final View toView = fromAllApps ? mHomeCustomizationDrawer : (View) mAllAppsGrid;
+
+ final float fromViewStartY = fromAllApps ? 0.0f : fromView.getY();
+ final float fromViewEndY = fromAllApps ? -fromView.getHeight() * 2 : workspaceHeight * 2;
+ final float toViewStartY = fromAllApps ? workspaceHeight * 2 : -toView.getHeight() * 2;
+ final float toViewEndY = fromAllApps ? workspaceHeight - toView.getHeight() : 0.0f;
+
+ mCustomizePagedView.endChoiceMode();
+ mAllAppsPagedView.endChoiceMode();
+
+ if (toState == State.ALL_APPS) {
+ mWorkspace.shrinkToBottom(animated);
+ } else {
+ mWorkspace.shrinkToTop(animated);
+ }
+
+ if (animated) {
+ if (mStateAnimation != null) mStateAnimation.cancel();
+ mStateAnimation = new AnimatorSet();
+ mStateAnimation.addListener(new LauncherAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ toView.setVisibility(View.VISIBLE);
+ toView.setY(toViewStartY);
+ }
+ @Override
+ public void onAnimationEndOrCancel(Animator animation) {
+ fromView.setVisibility(View.GONE);
+ }
+ });
+
+ AnimatorSet toolbarHideAnim = new AnimatorSet();
+ AnimatorSet toolbarShowAnim = new AnimatorSet();
+ hideAndShowToolbarButtons(toState, toolbarShowAnim, toolbarHideAnim);
+
+ ObjectAnimator fromAnim = ObjectAnimator.ofFloat(fromView, "y",
+ fromViewStartY, fromViewEndY);
+ fromAnim.setDuration(duration);
+ ObjectAnimator toAnim = ObjectAnimator.ofPropertyValuesHolder(toView,
+ PropertyValuesHolder.ofFloat("y", toViewStartY, toViewEndY),
+ PropertyValuesHolder.ofFloat("scaleX", toView.getScaleX(), 1.0f),
+ PropertyValuesHolder.ofFloat("scaleY", toView.getScaleY(), 1.0f)
+ );
+ fromAnim.setDuration(duration);
+ mStateAnimation.playTogether(toolbarHideAnim, fromAnim, toAnim);
+
+ // Show the new toolbar buttons just as the main animation is ending
+ final int fadeInTime = res.getInteger(R.integer.config_toolbarButtonFadeInTime);
+ mStateAnimation.play(toolbarShowAnim).after(duration - fadeInTime);
+ mStateAnimation.start();
+ } else {
+ fromView.setY(fromViewEndY);
+ fromView.setVisibility(View.GONE);
+ toView.setY(toViewEndY);
+ toView.setScaleX(1.0f);
+ toView.setScaleY(1.0f);
+ toView.setVisibility(View.VISIBLE);
+ hideAndShowToolbarButtons(toState, null, null);
+ }
+ }
void showAllApps(boolean animated) {
- mAllAppsGrid.zoom(1.0f, animated);
+ if (mState == State.ALL_APPS) {
+ return;
+ }
+
+ if (LauncherApplication.isScreenXLarge()) {
+ if (mState == State.CUSTOMIZE) {
+ cameraPan(State.CUSTOMIZE, State.ALL_APPS, animated);
+ } else {
+ cameraZoomOut(State.ALL_APPS, animated);
+ }
+ } else {
+ mAllAppsGrid.zoom(1.0f, animated);
+ }
((View) mAllAppsGrid).setFocusable(true);
((View) mAllAppsGrid).requestFocus();
-
+
// TODO: fade these two too
mDeleteZone.setVisibility(View.GONE);
+ // Change the state *after* we've called all the transition code
+ mState = State.ALL_APPS;
+ }
+
+
+ void showWorkspace(boolean animated) {
+ showWorkspace(animated, null);
+ }
+
+ void showWorkspace(boolean animated, CellLayout layout) {
+ if (layout != null && animated) {
+ mWorkspace.unshrink(layout);
+ } else {
+ mWorkspace.unshrink(animated);
+ }
+ if (mState == State.ALL_APPS) {
+ closeAllApps(animated);
+ } else if (mState == State.CUSTOMIZE) {
+ hideCustomizationDrawer(animated);
+ }
+ // Change the state *after* we've called all the transition code
+ mState = State.WORKSPACE;
}
/**
@@ -1975,11 +2681,15 @@ public final class Launcher extends Activity
* - From another workspace
*/
void closeAllApps(boolean animated) {
- if (mAllAppsGrid.isVisible()) {
+ if (mState == State.ALL_APPS) {
mWorkspace.setVisibility(View.VISIBLE);
- mAllAppsGrid.zoom(0.0f, animated);
+ if (LauncherApplication.isScreenXLarge()) {
+ cameraZoomIn(State.ALL_APPS, animated);
+ } else {
+ mAllAppsGrid.zoom(0.0f, animated);
+ }
((View)mAllAppsGrid).setFocusable(false);
- mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus();
+ mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
}
}
@@ -1991,6 +2701,90 @@ public final class Launcher extends Activity
// TODO
}
+ // Show the customization drawer (only exists in x-large configuration)
+ private void showCustomizationDrawer(boolean animated) {
+ if (mState == State.ALL_APPS) {
+ cameraPan(State.ALL_APPS, State.CUSTOMIZE, animated);
+ } else {
+ cameraZoomOut(State.CUSTOMIZE, animated);
+ }
+ // Change the state *after* we've called all the transition code
+ mState = State.CUSTOMIZE;
+ }
+
+ // Hide the customization drawer (only exists in x-large configuration)
+ void hideCustomizationDrawer(boolean animated) {
+ if (mState == State.CUSTOMIZE) {
+ cameraZoomIn(State.CUSTOMIZE, animated);
+ }
+ }
+
+ void addExternalItemToScreen(ItemInfo itemInfo, CellLayout layout) {
+ if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) {
+ showOutOfSpaceMessage();
+ }
+ }
+
+ void onWorkspaceClick(CellLayout layout) {
+ showWorkspace(true, layout);
+ }
+
+ private void updateButtonWithIconFromExternalActivity(
+ int buttonId, ComponentName activityName, int fallbackDrawableId) {
+ ImageView button = (ImageView) findViewById(buttonId);
+ Drawable toolbarIcon = null;
+ try {
+ PackageManager packageManager = getPackageManager();
+ // Look for the toolbar icon specified in the activity meta-data
+ Bundle metaData = packageManager.getActivityInfo(
+ activityName, PackageManager.GET_META_DATA).metaData;
+ if (metaData != null) {
+ int iconResId = metaData.getInt(TOOLBAR_ICON_METADATA_NAME);
+ if (iconResId != 0) {
+ Resources res = packageManager.getResourcesForActivity(activityName);
+ toolbarIcon = res.getDrawable(iconResId);
+ }
+ }
+ } catch (NameNotFoundException e) {
+ // Do nothing
+ }
+ // If we were unable to find the icon via the meta-data, use a generic one
+ if (toolbarIcon == null) {
+ button.setImageResource(fallbackDrawableId);
+ } else {
+ button.setImageDrawable(toolbarIcon);
+ }
+ }
+
+ private void updateGlobalSearchIcon() {
+ if (LauncherApplication.isScreenXLarge()) {
+ final SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ ComponentName activityName = searchManager.getGlobalSearchActivity();
+ if (activityName != null) {
+ updateButtonWithIconFromExternalActivity(
+ R.id.search_button, activityName, R.drawable.search_button_generic);
+ }
+ }
+ }
+
+ /**
+ * Sets the app market icon (shown when all apps is visible on x-large screens)
+ */
+ private void updateAppMarketIcon() {
+ if (LauncherApplication.isScreenXLarge()) {
+ Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET);
+ // Find the app market activity by resolving an intent.
+ // (If multiple app markets are installed, it will return the ResolverActivity.)
+ ComponentName activityName = intent.resolveActivity(getPackageManager());
+ if (activityName != null) {
+ mAppMarketIntent = intent;
+ updateButtonWithIconFromExternalActivity(
+ R.id.market_button, activityName, R.drawable.app_market_generic);
+ }
+ }
+ }
+
/**
* Displays the shortcut creation dialog and launches, if necessary, the
* appropriate activity.
@@ -2043,7 +2837,6 @@ public final class Launcher extends Activity
switch (which) {
case AddAdapter.ITEM_SHORTCUT: {
- // Insert extra item to handle picking application
pickShortcut();
break;
}
@@ -2091,7 +2884,7 @@ public final class Launcher extends Activity
}
public void onShow(DialogInterface dialog) {
- mWaitingForResult = true;
+ mWaitingForResult = true;
}
}
@@ -2108,7 +2901,7 @@ public final class Launcher extends Activity
if (mPaused || "lock".equals(reason)) {
animate = false;
}
- closeAllApps(animate);
+ showWorkspace(animate);
}
}
}
@@ -2156,12 +2949,16 @@ public final class Launcher extends Activity
*/
public int getCurrentWorkspaceScreen() {
if (mWorkspace != null) {
- return mWorkspace.getCurrentScreen();
+ return mWorkspace.getCurrentPage();
} else {
return SCREEN_COUNT / 2;
}
}
+ void setAllAppsPagedView(PagedView view) {
+ mAllAppsPagedView = view;
+ }
+
/**
* Refreshes the shortcuts shown on the workspace.
*
@@ -2211,15 +3008,15 @@ public final class Launcher extends Activity
break;
case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
final FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
- (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()),
- (UserFolderInfo) item);
+ (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
+ (UserFolderInfo) item, mIconCache);
workspace.addInScreen(newFolder, item.screen, item.cellX, item.cellY, 1, 1,
false);
break;
case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
final FolderIcon newLiveFolder = LiveFolderIcon.fromXml(
R.layout.live_folder_icon, this,
- (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()),
+ (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
(LiveFolderInfo) item);
workspace.addInScreen(newLiveFolder, item.screen, item.cellX, item.cellY, 1, 1,
false);
@@ -2287,7 +3084,7 @@ public final class Launcher extends Activity
if (mSavedState != null) {
if (!mWorkspace.hasFocus()) {
- mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus();
+ mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
}
final long[] userFolders = mSavedState.getLongArray(RUNTIME_STATE_USER_FOLDERS);
@@ -2322,6 +3119,11 @@ public final class Launcher extends Activity
*/
public void bindAllApplications(ArrayList<ApplicationInfo> apps) {
mAllAppsGrid.setApps(apps);
+ if (mCustomizePagedView != null) {
+ mCustomizePagedView.setApps(apps);
+ }
+ updateAppMarketIcon();
+ updateGlobalSearchIcon();
}
/**
@@ -2333,6 +3135,11 @@ public final class Launcher extends Activity
setLoadOnResume();
removeDialog(DIALOG_CREATE_SHORTCUT);
mAllAppsGrid.addApps(apps);
+ if (mCustomizePagedView != null) {
+ mCustomizePagedView.addApps(apps);
+ }
+ updateAppMarketIcon();
+ updateGlobalSearchIcon();
}
/**
@@ -2345,6 +3152,11 @@ public final class Launcher extends Activity
removeDialog(DIALOG_CREATE_SHORTCUT);
mWorkspace.updateShortcuts(apps);
mAllAppsGrid.updateApps(apps);
+ if (mCustomizePagedView != null) {
+ mCustomizePagedView.updateApps(apps);
+ }
+ updateAppMarketIcon();
+ updateGlobalSearchIcon();
}
/**
@@ -2358,6 +3170,21 @@ public final class Launcher extends Activity
mWorkspace.removeItems(apps);
}
mAllAppsGrid.removeApps(apps);
+ if (mCustomizePagedView != null) {
+ mCustomizePagedView.removeApps(apps);
+ }
+ updateAppMarketIcon();
+ updateGlobalSearchIcon();
+ }
+
+ /**
+ * A number of packages were updated.
+ */
+ public void bindPackagesUpdated() {
+ // update the customization drawer contents
+ if (mCustomizePagedView != null) {
+ mCustomizePagedView.update();
+ }
}
/**
diff --git a/src/com/android/launcher2/LauncherAnimatorListenerAdapter.java b/src/com/android/launcher2/LauncherAnimatorListenerAdapter.java
new file mode 100644
index 000000000..3ab4868e3
--- /dev/null
+++ b/src/com/android/launcher2/LauncherAnimatorListenerAdapter.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import android.animation.Animator;
+
+import java.util.HashSet;
+
+/**
+ * This adapter class provides empty implementations of the methods from {@link android.animation.Animator.AnimatorListener}.
+ * Any custom listener that cares only about a subset of the methods of this listener can
+ * simply subclass this adapter class instead of implementing the interface directly.
+ */
+public abstract class LauncherAnimatorListenerAdapter implements Animator.AnimatorListener {
+ HashSet<Animator> cancelled = new HashSet<Animator>();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final void onAnimationCancel(Animator animation) {
+ onAnimationEndOrCancel(animation);
+ cancelled.add(animation);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final void onAnimationEnd(Animator animation) {
+ if (!cancelled.contains(animation)) onAnimationEndOrCancel(animation);
+ cancelled.remove(animation);
+ }
+
+ /**
+ * Like onAnimationEnd, except it's called immediately in the case on onAnimationCancel, and
+ * it's only called once in that case
+ */
+ public void onAnimationEndOrCancel(Animator animation) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+
+}
diff --git a/src/com/android/launcher2/LauncherAppWidgetHost.java b/src/com/android/launcher2/LauncherAppWidgetHost.java
index a5761ecc3..46e66e7ac 100644
--- a/src/com/android/launcher2/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher2/LauncherAppWidgetHost.java
@@ -30,7 +30,7 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
public LauncherAppWidgetHost(Context context, int hostId) {
super(context, hostId);
}
-
+
@Override
protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
diff --git a/src/com/android/launcher2/LauncherAppWidgetInfo.java b/src/com/android/launcher2/LauncherAppWidgetInfo.java
index 8499ebb7c..32c92aabd 100644
--- a/src/com/android/launcher2/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher2/LauncherAppWidgetInfo.java
@@ -17,25 +17,50 @@
package com.android.launcher2;
import android.appwidget.AppWidgetHostView;
+import android.content.ComponentName;
import android.content.ContentValues;
/**
- * Represents a widget, which just contains an identifier.
+ * Represents a widget (either instantiated or about to be) in the Launcher.
*/
class LauncherAppWidgetInfo extends ItemInfo {
/**
+ * Indicates that the widget hasn't been instantiated yet.
+ */
+ static final int NO_ID = -1;
+
+ /**
* Identifier for this widget when talking with
* {@link android.appwidget.AppWidgetManager} for updates.
*/
- int appWidgetId;
+ int appWidgetId = NO_ID;
+
+ ComponentName providerName;
+ // TODO: Are these necessary here?
+ int minWidth = -1;
+ int minHeight = -1;
+
/**
* View that holds this widget after it's been created. This view isn't created
* until Launcher knows it's needed.
*/
AppWidgetHostView hostView = null;
+ /**
+ * Constructor for use with AppWidgets that haven't been instantiated yet.
+ */
+ LauncherAppWidgetInfo(ComponentName providerName) {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+ this.providerName = providerName;
+
+ // Since the widget isn't instantiated yet, we don't know these values. Set them to -1
+ // to indicate that they should be calculated based on the layout and minWidth/minHeight
+ spanX = -1;
+ spanY = -1;
+ }
+
LauncherAppWidgetInfo(int appWidgetId) {
itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
this.appWidgetId = appWidgetId;
@@ -52,7 +77,6 @@ class LauncherAppWidgetInfo extends ItemInfo {
return "AppWidget(id=" + Integer.toString(appWidgetId) + ")";
}
-
@Override
void unbind() {
super.unbind();
diff --git a/src/com/android/launcher2/LauncherApplication.java b/src/com/android/launcher2/LauncherApplication.java
index eda92d999..ed007dd67 100644
--- a/src/com/android/launcher2/LauncherApplication.java
+++ b/src/com/android/launcher2/LauncherApplication.java
@@ -20,6 +20,7 @@ import android.app.Application;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Configuration;
import android.database.ContentObserver;
import android.os.Handler;
import dalvik.system.VMRuntime;
@@ -27,6 +28,9 @@ import dalvik.system.VMRuntime;
public class LauncherApplication extends Application {
public LauncherModel mModel;
public IconCache mIconCache;
+ private static boolean sIsScreenXLarge;
+ private static float sScreenDensity;
+ private static final boolean ENABLE_ROTATION = false;
@Override
public void onCreate() {
@@ -34,6 +38,10 @@ public class LauncherApplication extends Application {
super.onCreate();
+ // set sIsScreenXLarge and sScreenDensity *before* creating icon cache
+ sIsScreenXLarge = (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ sScreenDensity = getResources().getDisplayMetrics().density;
+
mIconCache = new IconCache(this);
mModel = new LauncherModel(this, mIconCache);
@@ -46,6 +54,7 @@ public class LauncherApplication extends Application {
filter = new IntentFilter();
filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ filter.addAction(Intent.ACTION_LOCALE_CHANGED);
registerReceiver(mModel, filter);
// Register for changes to the favorites
@@ -89,4 +98,16 @@ public class LauncherApplication extends Application {
LauncherModel getModel() {
return mModel;
}
+
+ public static boolean isInPlaceRotationEnabled() {
+ return sIsScreenXLarge && ENABLE_ROTATION;
+ }
+
+ public static boolean isScreenXLarge() {
+ return sIsScreenXLarge;
+ }
+
+ public static float getScreenDensity() {
+ return sScreenDensity;
+ }
}
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index b819510af..713268ad6 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -16,6 +16,15 @@
package com.android.launcher2;
+import java.lang.ref.WeakReference;
+import java.net.URISyntaxException;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
@@ -23,9 +32,9 @@ import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
+import android.content.Context;
import android.content.Intent;
import android.content.Intent.ShortcutIconResource;
-import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
@@ -38,22 +47,13 @@ import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Parcelable;
-import android.os.RemoteException;
-import android.util.Log;
import android.os.Process;
+import android.os.RemoteException;
import android.os.SystemClock;
-
-import java.lang.ref.WeakReference;
-import java.net.URISyntaxException;
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
+import android.util.Log;
import com.android.launcher.R;
+import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
/**
* Maintains in-memory state of the Launcher. It is expected that there should be only one
@@ -95,6 +95,9 @@ public class LauncherModel extends BroadcastReceiver {
private Bitmap mDefaultIcon;
+ private static int mCellCountX;
+ private static int mCellCountY;
+
public interface Callbacks {
public boolean setLoadOnResume();
public int getCurrentWorkspaceScreen();
@@ -107,6 +110,7 @@ public class LauncherModel extends BroadcastReceiver {
public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent);
+ public void bindPackagesUpdated();
public boolean isAllAppsVisible();
}
@@ -116,7 +120,7 @@ public class LauncherModel extends BroadcastReceiver {
mIconCache = iconCache;
mDefaultIcon = Utilities.createIconBitmap(
- app.getPackageManager().getDefaultActivityIcon(), app);
+ mIconCache.getFullResDefaultActivityIcon(), app);
mAllAppsLoadDelay = app.getResources().getInteger(R.integer.config_allAppsBatchLoadDelay);
@@ -147,6 +151,7 @@ public class LauncherModel extends BroadcastReceiver {
*/
static void moveItemInDatabase(Context context, ItemInfo item, long container, int screen,
int cellX, int cellY) {
+
item.container = container;
item.screen = screen;
item.cellX = cellX;
@@ -157,8 +162,8 @@ public class LauncherModel extends BroadcastReceiver {
final ContentResolver cr = context.getContentResolver();
values.put(LauncherSettings.Favorites.CONTAINER, item.container);
- values.put(LauncherSettings.Favorites.CELLX, item.cellX);
- values.put(LauncherSettings.Favorites.CELLY, item.cellY);
+ values.put(LauncherSettings.Favorites.CELLX, cellX);
+ values.put(LauncherSettings.Favorites.CELLY, cellY);
values.put(LauncherSettings.Favorites.SCREEN, item.screen);
sWorker.post(new Runnable() {
@@ -187,6 +192,48 @@ public class LauncherModel extends BroadcastReceiver {
}
/**
+ * Returns an ItemInfo array containing all the items in the LauncherModel.
+ * The ItemInfo.id is not set through this function.
+ */
+ static ArrayList<ItemInfo> getItemsInLocalCoordinates(Context context) {
+ ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
+ final ContentResolver cr = context.getContentResolver();
+ Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, new String[] {
+ LauncherSettings.Favorites.ITEM_TYPE, LauncherSettings.Favorites.CONTAINER,
+ LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY,
+ LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY }, null, null, null);
+
+ final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
+ final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
+ final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
+ final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
+ final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
+ final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
+ final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
+
+ try {
+ while (c.moveToNext()) {
+ ItemInfo item = new ItemInfo();
+ item.cellX = c.getInt(cellXIndex);
+ item.cellY = c.getInt(cellYIndex);
+ item.spanX = c.getInt(spanXIndex);
+ item.spanY = c.getInt(spanYIndex);
+ item.container = c.getInt(containerIndex);
+ item.itemType = c.getInt(itemTypeIndex);
+ item.screen = c.getInt(screenIndex);
+
+ items.add(item);
+ }
+ } catch (Exception e) {
+ items.clear();
+ } finally {
+ c.close();
+ }
+
+ return items;
+ }
+
+ /**
* Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
*/
FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) {
@@ -245,9 +292,10 @@ public class LauncherModel extends BroadcastReceiver {
final ContentValues values = new ContentValues();
final ContentResolver cr = context.getContentResolver();
-
item.onAddToDatabase(values);
+ item.updateValuesWithCoordinates(values, cellX, cellY);
+
Uri result = cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
@@ -257,6 +305,32 @@ public class LauncherModel extends BroadcastReceiver {
}
/**
+ * Creates a new unique child id, for a given cell span across all layouts.
+ */
+ static int getCellLayoutChildId(
+ int cellId, int screen, int localCellX, int localCellY, int spanX, int spanY) {
+ return ((cellId & 0xFF) << 24)
+ | (screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
+ }
+
+ static int getCellCountX() {
+ return mCellCountX;
+ }
+
+ static int getCellCountY() {
+ return mCellCountY;
+ }
+
+ /**
+ * Updates the model orientation helper to take into account the current layout dimensions
+ * when performing local/canonical coordinate transformations.
+ */
+ static void updateWorkspaceLayoutCells(int shortAxisCellCount, int longAxisCellCount) {
+ mCellCountX = shortAxisCellCount;
+ mCellCountY = longAxisCellCount;
+ }
+
+ /**
* Update an item to the database in a specified container.
*/
static void updateItemInDatabase(Context context, ItemInfo item) {
@@ -264,6 +338,7 @@ public class LauncherModel extends BroadcastReceiver {
final ContentResolver cr = context.getContentResolver();
item.onAddToDatabase(values);
+ item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null);
}
@@ -309,7 +384,7 @@ public class LauncherModel extends BroadcastReceiver {
*/
public void onReceive(Context context, Intent intent) {
if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
-
+
final String action = intent.getAction();
if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
@@ -350,25 +425,41 @@ public class LauncherModel extends BroadcastReceiver {
String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages));
// Then, rebind everything.
- boolean runLoader = true;
- if (mCallbacks != null) {
- Callbacks callbacks = mCallbacks.get();
- if (callbacks != null) {
- // If they're paused, we can skip loading, because they'll do it again anyway
- if (callbacks.setLoadOnResume()) {
- runLoader = false;
- }
- }
- }
- if (runLoader) {
- startLoader(mApp, false);
- }
-
+ startLoaderFromBackground();
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
enqueuePackageUpdated(new PackageUpdatedTask(
PackageUpdatedTask.OP_UNAVAILABLE, packages));
+ } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+ // If we have changed locale we need to clear out the labels in all apps.
+ // Do this here because if the launcher activity is running it will be restarted.
+ // If it's not running startLoaderFromBackground will merely tell it that it needs
+ // to reload. Either way, mAllAppsLoaded will be cleared so it re-reads everything
+ // next time.
+ mAllAppsLoaded = false;
+ startLoaderFromBackground();
+ }
+ }
+ /**
+ * When the launcher is in the background, it's possible for it to miss paired
+ * configuration changes. So whenever we trigger the loader from the background
+ * tell the launcher that it needs to re-run the loader when it comes back instead
+ * of doing it now.
+ */
+ public void startLoaderFromBackground() {
+ boolean runLoader = false;
+ if (mCallbacks != null) {
+ Callbacks callbacks = mCallbacks.get();
+ if (callbacks != null) {
+ // Only actually run the loader if they're not paused.
+ if (!callbacks.setLoadOnResume()) {
+ runLoader = true;
+ }
+ }
+ }
+ if (runLoader) {
+ startLoader(mApp, false);
}
}
@@ -474,7 +565,7 @@ public class LauncherModel extends BroadcastReceiver {
}
if (DEBUG_LOADERS) {
Log.d(TAG, "waited "
- + (SystemClock.uptimeMillis()-workspaceWaitTime)
+ + (SystemClock.uptimeMillis()-workspaceWaitTime)
+ "ms for previous step to finish binding");
}
}
@@ -494,7 +585,6 @@ public class LauncherModel extends BroadcastReceiver {
android.os.Process.setThreadPriority(mIsLaunching
? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
}
-
if (loadWorkspaceFirst) {
if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
loadAndBindWorkspace();
@@ -596,14 +686,13 @@ public class LauncherModel extends BroadcastReceiver {
if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
return true;
}
-
for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
if (occupied[item.screen][x][y] != null) {
Log.e(TAG, "Error loading shortcut " + item
- + " into cell (" + item.screen + ":"
+ + " into cell (" + item.screen + ":"
+ x + "," + y
- + ") occupied by "
+ + ") occupied by "
+ occupied[item.screen][x][y]);
return false;
}
@@ -635,7 +724,8 @@ public class LauncherModel extends BroadcastReceiver {
final Cursor c = contentResolver.query(
LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
- final ItemInfo occupied[][][] = new ItemInfo[Launcher.SCREEN_COUNT][Launcher.NUMBER_CELLS_X][Launcher.NUMBER_CELLS_Y];
+ final ItemInfo occupied[][][] =
+ new ItemInfo[Launcher.SCREEN_COUNT][mCellCountX][mCellCountY];
try {
final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
@@ -744,7 +834,6 @@ public class LauncherModel extends BroadcastReceiver {
UserFolderInfo folderInfo = findOrMakeUserFolder(mFolders, id);
folderInfo.title = c.getString(titleIndex);
-
folderInfo.id = id;
container = c.getInt(containerIndex);
folderInfo.container = container;
@@ -756,7 +845,6 @@ public class LauncherModel extends BroadcastReceiver {
if (!checkItemPlacement(occupied, folderInfo)) {
break;
}
-
switch (container) {
case LauncherSettings.Favorites.CONTAINER_DESKTOP:
mItems.add(folderInfo);
@@ -779,7 +867,6 @@ public class LauncherModel extends BroadcastReceiver {
itemsToRemove.add(id);
} else {
LiveFolderInfo liveFolderInfo = findOrMakeLiveFolder(mFolders, id);
-
intentDescription = c.getString(intentIndex);
intent = null;
if (intentDescription != null) {
@@ -825,7 +912,7 @@ public class LauncherModel extends BroadcastReceiver {
final AppWidgetProviderInfo provider =
widgets.getAppWidgetInfo(appWidgetId);
-
+
if (!isSafeMode && (provider == null || provider.provider == null ||
provider.provider.getPackageName() == null)) {
Log.e(TAG, "Deleting widget that isn't installed anymore: id="
@@ -886,13 +973,13 @@ public class LauncherModel extends BroadcastReceiver {
if (DEBUG_LOADERS) {
Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
Log.d(TAG, "workspace layout: ");
- for (int y = 0; y < Launcher.NUMBER_CELLS_Y; y++) {
+ for (int y = 0; y < mCellCountY; y++) {
String line = "";
for (int s = 0; s < Launcher.SCREEN_COUNT; s++) {
if (s > 0) {
line += " | ";
}
- for (int x = 0; x < Launcher.NUMBER_CELLS_X; x++) {
+ for (int x = 0; x < mCellCountX; x++) {
line += ((occupied[s][x][y] != null) ? "#" : ".");
}
}
@@ -1116,7 +1203,7 @@ public class LauncherModel extends BroadcastReceiver {
startIndex = i;
for (int j=0; i<N && j<batchSize; j++) {
// This builds the icon bitmaps.
- mAllAppsList.add(new ApplicationInfo(apps.get(i), mIconCache));
+ mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i), mIconCache));
i++;
}
@@ -1279,6 +1366,15 @@ public class LauncherModel extends BroadcastReceiver {
}
});
}
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (callbacks == mCallbacks.get()) {
+ callbacks.bindPackagesUpdated();
+ }
+ }
+ });
}
}
@@ -1374,7 +1470,8 @@ public class LauncherModel extends BroadcastReceiver {
Resources resources = packageManager.getResourcesForApplication(packageName);
if (resources != null) {
final int id = resources.getIdentifier(resourceName, null, null);
- icon = Utilities.createIconBitmap(resources.getDrawable(id), context);
+ icon = Utilities.createIconBitmap(
+ mIconCache.getFullResIcon(resources, id), context);
}
} catch (Exception e) {
// drop this. we have other places to look for icons
@@ -1423,16 +1520,69 @@ public class LauncherModel extends BroadcastReceiver {
}
ShortcutInfo addShortcut(Context context, Intent data,
- CellLayout.CellInfo cellInfo, boolean notify) {
+ int screen, int cellX, int cellY, boolean notify) {
- final ShortcutInfo info = infoFromShortcutIntent(context, data);
+ final ShortcutInfo info = infoFromShortcutIntent(context, data, null);
addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
- cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
+ screen, cellX, cellY, notify);
return info;
}
- private ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
+ /**
+ * Attempts to find an AppWidgetProviderInfo that matches the given component.
+ */
+ AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context,
+ ComponentName component) {
+ List<AppWidgetProviderInfo> widgets =
+ AppWidgetManager.getInstance(context).getInstalledProviders();
+ for (AppWidgetProviderInfo info : widgets) {
+ if (info.provider.equals(component)) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a list of all the widgets that can handle configuration with a particular mimeType.
+ */
+ List<WidgetMimeTypeHandlerData> resolveWidgetsForMimeType(Context context, String mimeType) {
+ final PackageManager packageManager = context.getPackageManager();
+ final List<WidgetMimeTypeHandlerData> supportedConfigurationActivities =
+ new ArrayList<WidgetMimeTypeHandlerData>();
+
+ final Intent supportsIntent =
+ new Intent(InstallWidgetReceiver.ACTION_SUPPORTS_CLIPDATA_MIMETYPE);
+ supportsIntent.setType(mimeType);
+
+ // Create a set of widget configuration components that we can test against
+ final List<AppWidgetProviderInfo> widgets =
+ AppWidgetManager.getInstance(context).getInstalledProviders();
+ final HashMap<ComponentName, AppWidgetProviderInfo> configurationComponentToWidget =
+ new HashMap<ComponentName, AppWidgetProviderInfo>();
+ for (AppWidgetProviderInfo info : widgets) {
+ configurationComponentToWidget.put(info.configure, info);
+ }
+
+ // Run through each of the intents that can handle this type of clip data, and cross
+ // reference them with the components that are actual configuration components
+ final List<ResolveInfo> activities = packageManager.queryIntentActivities(supportsIntent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ for (ResolveInfo info : activities) {
+ final ActivityInfo activityInfo = info.activityInfo;
+ final ComponentName infoComponent = new ComponentName(activityInfo.packageName,
+ activityInfo.name);
+ if (configurationComponentToWidget.containsKey(infoComponent)) {
+ supportedConfigurationActivities.add(
+ new InstallWidgetReceiver.WidgetMimeTypeHandlerData(info,
+ configurationComponentToWidget.get(infoComponent)));
+ }
+ }
+ return supportedConfigurationActivities;
+ }
+
+ ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) {
Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
@@ -1455,7 +1605,8 @@ public class LauncherModel extends BroadcastReceiver {
Resources resources = packageManager.getResourcesForApplication(
iconResource.packageName);
final int id = resources.getIdentifier(iconResource.resourceName, null, null);
- icon = Utilities.createIconBitmap(resources.getDrawable(id), context);
+ icon = Utilities.createIconBitmap(
+ mIconCache.getFullResIcon(resources, id), context);
} catch (Exception e) {
Log.w(TAG, "Could not load shortcut icon: " + extra);
}
@@ -1465,8 +1616,12 @@ public class LauncherModel extends BroadcastReceiver {
final ShortcutInfo info = new ShortcutInfo();
if (icon == null) {
- icon = getFallbackIcon();
- info.usingFallbackIcon = true;
+ if (fallbackIcon != null) {
+ icon = fallbackIcon;
+ } else {
+ icon = getFallbackIcon();
+ info.usingFallbackIcon = true;
+ }
}
info.setIcon(icon);
@@ -1478,7 +1633,7 @@ public class LauncherModel extends BroadcastReceiver {
return info;
}
- private static void loadLiveFolderIcon(Context context, Cursor c, int iconTypeIndex,
+ private void loadLiveFolderIcon(Context context, Cursor c, int iconTypeIndex,
int iconPackageIndex, int iconResourceIndex, LiveFolderInfo liveFolderInfo) {
int iconType = c.getInt(iconTypeIndex);
@@ -1488,13 +1643,14 @@ public class LauncherModel extends BroadcastReceiver {
String resourceName = c.getString(iconResourceIndex);
PackageManager packageManager = context.getPackageManager();
try {
- Resources resources = packageManager.getResourcesForApplication(packageName);
- final int id = resources.getIdentifier(resourceName, null, null);
- liveFolderInfo.icon = Utilities.createIconBitmap(resources.getDrawable(id),
- context);
+ Resources appResources = packageManager.getResourcesForApplication(packageName);
+ final int id = appResources.getIdentifier(resourceName, null, null);
+ liveFolderInfo.icon = Utilities.createIconBitmap(
+ mIconCache.getFullResIcon(appResources, id), context);
} catch (Exception e) {
+ Resources resources = context.getResources();
liveFolderInfo.icon = Utilities.createIconBitmap(
- context.getResources().getDrawable(R.drawable.ic_launcher_folder),
+ mIconCache.getFullResIcon(resources, R.drawable.ic_launcher_folder),
context);
}
liveFolderInfo.iconResource = new Intent.ShortcutIconResource();
@@ -1502,8 +1658,9 @@ public class LauncherModel extends BroadcastReceiver {
liveFolderInfo.iconResource.resourceName = resourceName;
break;
default:
+ Resources resources = context.getResources();
liveFolderInfo.icon = Utilities.createIconBitmap(
- context.getResources().getDrawable(R.drawable.ic_launcher_folder),
+ mIconCache.getFullResIcon(resources, R.drawable.ic_launcher_folder),
context);
}
}
diff --git a/src/com/android/launcher2/LiveFolderInfo.java b/src/com/android/launcher2/LiveFolderInfo.java
index 7d0a0f58f..74b021758 100644
--- a/src/com/android/launcher2/LiveFolderInfo.java
+++ b/src/com/android/launcher2/LiveFolderInfo.java
@@ -18,7 +18,6 @@ package com.android.launcher2;
import android.content.ContentValues;
import android.content.Intent;
-import android.graphics.drawable.Drawable;
import android.graphics.Bitmap;
import android.net.Uri;
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
new file mode 100644
index 000000000..e9829fbfa
--- /dev/null
+++ b/src/com/android/launcher2/PagedView.java
@@ -0,0 +1,1272 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.ActionMode;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.AnimationUtils;
+import android.widget.Checkable;
+import android.widget.LinearLayout;
+import android.widget.Scroller;
+
+import com.android.launcher.R;
+
+/**
+ * An abstraction of the original Workspace which supports browsing through a
+ * sequential list of "pages"
+ */
+public abstract class PagedView extends ViewGroup {
+ private static final String TAG = "PagedView";
+ protected static final int INVALID_PAGE = -1;
+
+ // the min drag distance for a fling to register, to prevent random page shifts
+ private static final int MIN_LENGTH_FOR_FLING = 25;
+ // The min drag distance to trigger a page shift (regardless of velocity)
+ private static final int MIN_LENGTH_FOR_MOVE = 200;
+
+ private static final int PAGE_SNAP_ANIMATION_DURATION = 1000;
+ protected static final float NANOTIME_DIV = 1000000000.0f;
+
+ // the velocity at which a fling gesture will cause us to snap to the next page
+ protected int mSnapVelocity = 500;
+
+ protected float mSmoothingTime;
+ protected float mTouchX;
+
+ protected boolean mFirstLayout = true;
+
+ protected int mCurrentPage;
+ protected int mNextPage = INVALID_PAGE;
+ protected Scroller mScroller;
+ private VelocityTracker mVelocityTracker;
+
+ private float mDownMotionX;
+ private float mLastMotionX;
+ private float mLastMotionY;
+ private int mLastScreenCenter = -1;
+
+ protected final static int TOUCH_STATE_REST = 0;
+ protected final static int TOUCH_STATE_SCROLLING = 1;
+ protected final static int TOUCH_STATE_PREV_PAGE = 2;
+ protected final static int TOUCH_STATE_NEXT_PAGE = 3;
+ protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
+
+ protected int mTouchState = TOUCH_STATE_REST;
+
+ protected OnLongClickListener mLongClickListener;
+
+ private boolean mAllowLongPress = true;
+
+ private int mTouchSlop;
+ private int mPagingTouchSlop;
+ private int mMaximumVelocity;
+ protected int mPageSpacing;
+ protected int mPageLayoutPaddingTop;
+ protected int mPageLayoutPaddingBottom;
+ protected int mPageLayoutPaddingLeft;
+ protected int mPageLayoutPaddingRight;
+ protected int mPageLayoutWidthGap;
+ protected int mPageLayoutHeightGap;
+ protected int mCellCountX;
+ protected int mCellCountY;
+ protected boolean mCenterPagesVertically;
+
+ protected static final int INVALID_POINTER = -1;
+
+ protected int mActivePointerId = INVALID_POINTER;
+
+ private PageSwitchListener mPageSwitchListener;
+
+ private ArrayList<Boolean> mDirtyPageContent;
+ private boolean mDirtyPageAlpha;
+
+ // choice modes
+ protected static final int CHOICE_MODE_NONE = 0;
+ protected static final int CHOICE_MODE_SINGLE = 1;
+ // Multiple selection mode is not supported by all Launcher actions atm
+ protected static final int CHOICE_MODE_MULTIPLE = 2;
+
+ protected int mChoiceMode;
+ private ActionMode mActionMode;
+
+ protected PagedViewIconCache mPageViewIconCache;
+
+ // If true, syncPages and syncPageItems will be called to refresh pages
+ protected boolean mContentIsRefreshable = true;
+
+ // If true, modify alpha of neighboring pages as user scrolls left/right
+ protected boolean mFadeInAdjacentScreens = true;
+
+ // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
+ // to switch to a new page
+ protected boolean mUsePagingTouchSlop = true;
+
+ // If true, the subclass should directly update mScrollX itself in its computeScroll method
+ // (SmoothPagedView does this)
+ protected boolean mDeferScrollUpdate = false;
+
+ protected boolean mIsPageMoving = false;
+
+ /**
+ * Simple cache mechanism for PagedViewIcon outlines.
+ */
+ class PagedViewIconCache {
+ private final HashMap<Object, Bitmap> iconOutlineCache = new HashMap<Object, Bitmap>();
+
+ public void clear() {
+ iconOutlineCache.clear();
+ }
+ public void addOutline(Object key, Bitmap b) {
+ iconOutlineCache.put(key, b);
+ }
+ public void removeOutline(Object key) {
+ if (iconOutlineCache.containsKey(key)) {
+ iconOutlineCache.remove(key);
+ }
+ }
+ public Bitmap getOutline(Object key) {
+ return iconOutlineCache.get(key);
+ }
+ }
+
+ public interface PageSwitchListener {
+ void onPageSwitch(View newPage, int newPageIndex);
+ }
+
+ public PagedView(Context context) {
+ this(context, null);
+ }
+
+ public PagedView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PagedView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mChoiceMode = CHOICE_MODE_NONE;
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.PagedView, defStyle, 0);
+ mPageSpacing = a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0);
+ mPageLayoutPaddingTop = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingTop, 10);
+ mPageLayoutPaddingBottom = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingBottom, 10);
+ mPageLayoutPaddingLeft = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingLeft, 10);
+ mPageLayoutPaddingRight = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingRight, 10);
+ mPageLayoutWidthGap = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutWidthGap, -1);
+ mPageLayoutHeightGap = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutHeightGap, -1);
+ a.recycle();
+
+ setHapticFeedbackEnabled(false);
+ init();
+ }
+
+ /**
+ * Initializes various states for this workspace.
+ */
+ protected void init() {
+ mDirtyPageContent = new ArrayList<Boolean>();
+ mDirtyPageContent.ensureCapacity(32);
+ mPageViewIconCache = new PagedViewIconCache();
+ mScroller = new Scroller(getContext());
+ mCurrentPage = 0;
+ mCenterPagesVertically = true;
+
+ final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ }
+
+ public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
+ mPageSwitchListener = pageSwitchListener;
+ if (mPageSwitchListener != null) {
+ mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
+ }
+ }
+
+ /**
+ * Returns the index of the currently displayed page.
+ *
+ * @return The index of the currently displayed page.
+ */
+ int getCurrentPage() {
+ return mCurrentPage;
+ }
+
+ int getPageCount() {
+ return getChildCount();
+ }
+
+ View getPageAt(int index) {
+ return getChildAt(index);
+ }
+
+ int getScrollWidth() {
+ return getWidth();
+ }
+
+ /**
+ * Sets the current page.
+ */
+ void setCurrentPage(int currentPage) {
+ if (!mScroller.isFinished()) mScroller.abortAnimation();
+ if (getChildCount() == 0) return;
+
+ mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1));
+ int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage);
+ scrollTo(newX, 0);
+ mScroller.setFinalX(newX);
+
+ invalidate();
+ notifyPageSwitchListener();
+ }
+
+ protected void notifyPageSwitchListener() {
+ if (mPageSwitchListener != null) {
+ mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
+ }
+ }
+
+ private void pageBeginMoving() {
+ mIsPageMoving = true;
+ onPageBeginMoving();
+ }
+
+ private void pageEndMoving() {
+ onPageEndMoving();
+ mIsPageMoving = false;
+ }
+
+ // a method that subclasses can override to add behavior
+ protected void onPageBeginMoving() {
+ }
+
+ // a method that subclasses can override to add behavior
+ protected void onPageEndMoving() {
+ }
+
+ /**
+ * Registers the specified listener on each page contained in this workspace.
+ *
+ * @param l The listener used to respond to long clicks.
+ */
+ @Override
+ public void setOnLongClickListener(OnLongClickListener l) {
+ mLongClickListener = l;
+ final int count = getPageCount();
+ for (int i = 0; i < count; i++) {
+ getPageAt(i).setOnLongClickListener(l);
+ }
+ }
+
+ @Override
+ public void scrollTo(int x, int y) {
+ super.scrollTo(x, y);
+ mTouchX = x;
+ mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+ }
+
+ // we moved this functionality to a helper function so SmoothPagedView can reuse it
+ protected boolean computeScrollHelper() {
+ if (mScroller.computeScrollOffset()) {
+ mDirtyPageAlpha = true;
+ scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
+ invalidate();
+ return true;
+ } else if (mNextPage != INVALID_PAGE) {
+ mDirtyPageAlpha = true;
+ mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
+ mNextPage = INVALID_PAGE;
+ notifyPageSwitchListener();
+ pageEndMoving();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void computeScroll() {
+ computeScrollHelper();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ if (widthMode != MeasureSpec.EXACTLY) {
+ throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
+ }
+
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ if (heightMode != MeasureSpec.EXACTLY) {
+ throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
+ }
+
+ // The children are given the same width and height as the workspace
+ // unless they were set to WRAP_CONTENT
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ // disallowing padding in paged view (just pass 0)
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ int childWidthMode;
+ if (lp.width == LayoutParams.WRAP_CONTENT) {
+ childWidthMode = MeasureSpec.AT_MOST;
+ } else {
+ childWidthMode = MeasureSpec.EXACTLY;
+ }
+
+ int childHeightMode;
+ if (lp.height == LayoutParams.WRAP_CONTENT) {
+ childHeightMode = MeasureSpec.AT_MOST;
+ } else {
+ childHeightMode = MeasureSpec.EXACTLY;
+ }
+
+ final int childWidthMeasureSpec =
+ MeasureSpec.makeMeasureSpec(widthSize, childWidthMode);
+ final int childHeightMeasureSpec =
+ MeasureSpec.makeMeasureSpec(heightSize, childHeightMode);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ setMeasuredDimension(widthSize, heightSize);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
+ setHorizontalScrollBarEnabled(false);
+ int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage);
+ scrollTo(newX, 0);
+ mScroller.setFinalX(newX);
+ setHorizontalScrollBarEnabled(true);
+ mFirstLayout = false;
+ }
+
+ final int childCount = getChildCount();
+ int childLeft = 0;
+ if (childCount > 0) {
+ childLeft = getRelativeChildOffset(0);
+ }
+
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != View.GONE) {
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = (mCenterPagesVertically ?
+ (getMeasuredHeight() - child.getMeasuredHeight()) / 2 : 0);
+ child.layout(childLeft, childHeight,
+ childLeft + childWidth, childHeight + child.getMeasuredHeight());
+ childLeft += childWidth + mPageSpacing;
+ }
+ }
+ }
+
+ protected void updateAdjacentPagesAlpha() {
+ if (mFadeInAdjacentScreens) {
+ if (mDirtyPageAlpha || (mTouchState == TOUCH_STATE_SCROLLING) || !mScroller.isFinished()) {
+ int halfScreenSize = getMeasuredWidth() / 2;
+ int screenCenter = mScrollX + halfScreenSize;
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; ++i) {
+ View layout = (View) getChildAt(i);
+ int childWidth = layout.getMeasuredWidth();
+ int halfChildWidth = (childWidth / 2);
+ int childCenter = getChildOffset(i) + halfChildWidth;
+
+ // On the first layout, we may not have a width nor a proper offset, so for now
+ // we should just assume full page width (and calculate the offset according to
+ // that).
+ if (childWidth <= 0) {
+ childWidth = getMeasuredWidth();
+ childCenter = (i * childWidth) + (childWidth / 2);
+ }
+
+ int d = halfChildWidth;
+ int distanceFromScreenCenter = childCenter - screenCenter;
+ if (distanceFromScreenCenter > 0) {
+ if (i > 0) {
+ d += getChildAt(i - 1).getMeasuredWidth() / 2;
+ }
+ } else {
+ if (i < childCount - 1) {
+ d += getChildAt(i + 1).getMeasuredWidth() / 2;
+ }
+ }
+ d += mPageSpacing;
+
+ // Preventing potential divide-by-zero
+ d = Math.max(1, d);
+
+ float dimAlpha = (float) (Math.abs(distanceFromScreenCenter)) / d;
+ dimAlpha = Math.max(0.0f, Math.min(1.0f, (dimAlpha * dimAlpha)));
+ float alpha = 1.0f - dimAlpha;
+
+ if (alpha < ALPHA_QUANTIZE_LEVEL) {
+ alpha = 0.0f;
+ } else if (alpha > 1.0f - ALPHA_QUANTIZE_LEVEL) {
+ alpha = 1.0f;
+ }
+
+ if (Float.compare(alpha, layout.getAlpha()) != 0) {
+ layout.setAlpha(alpha);
+ }
+ }
+ mDirtyPageAlpha = false;
+ }
+ }
+ }
+
+ protected void screenScrolled(int screenCenter) {
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ int halfScreenSize = getMeasuredWidth() / 2;
+ int screenCenter = mScrollX + halfScreenSize;
+
+ if (screenCenter != mLastScreenCenter) {
+ screenScrolled(screenCenter);
+ updateAdjacentPagesAlpha();
+ mLastScreenCenter = screenCenter;
+ }
+
+ // Find out which screens are visible; as an optimization we only call draw on them
+ // As an optimization, this code assumes that all pages have the same width as the 0th
+ // page.
+ final int pageCount = getChildCount();
+ if (pageCount > 0) {
+ final int pageWidth = getChildAt(0).getMeasuredWidth();
+ final int screenWidth = getMeasuredWidth();
+ int x = getRelativeChildOffset(0) + pageWidth;
+ int leftScreen = 0;
+ int rightScreen = 0;
+ while (x <= mScrollX) {
+ leftScreen++;
+ x += pageWidth + mPageSpacing;
+ // replace above line with this if you don't assume all pages have same width as 0th
+ // page:
+ // x += getChildAt(leftScreen).getMeasuredWidth();
+ }
+ rightScreen = leftScreen;
+ while (x < mScrollX + screenWidth) {
+ rightScreen++;
+ x += pageWidth + mPageSpacing;
+ // replace above line with this if you don't assume all pages have same width as 0th
+ // page:
+ //if (rightScreen < pageCount) {
+ // x += getChildAt(rightScreen).getMeasuredWidth();
+ //}
+ }
+ rightScreen = Math.min(getChildCount() - 1, rightScreen);
+
+ final long drawingTime = getDrawingTime();
+ for (int i = leftScreen; i <= rightScreen; i++) {
+ drawChild(canvas, getChildAt(i), drawingTime);
+ }
+ }
+ }
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+ int page = indexOfChild(child);
+ if (page != mCurrentPage || !mScroller.isFinished()) {
+ snapToPage(page);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ int focusablePage;
+ if (mNextPage != INVALID_PAGE) {
+ focusablePage = mNextPage;
+ } else {
+ focusablePage = mCurrentPage;
+ }
+ View v = getPageAt(focusablePage);
+ if (v != null) {
+ v.requestFocus(direction, previouslyFocusedRect);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchUnhandledMove(View focused, int direction) {
+ if (direction == View.FOCUS_LEFT) {
+ if (getCurrentPage() > 0) {
+ snapToPage(getCurrentPage() - 1);
+ return true;
+ }
+ } else if (direction == View.FOCUS_RIGHT) {
+ if (getCurrentPage() < getPageCount() - 1) {
+ snapToPage(getCurrentPage() + 1);
+ return true;
+ }
+ }
+ return super.dispatchUnhandledMove(focused, direction);
+ }
+
+ @Override
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
+ getPageAt(mCurrentPage).addFocusables(views, direction);
+ }
+ if (direction == View.FOCUS_LEFT) {
+ if (mCurrentPage > 0) {
+ getPageAt(mCurrentPage - 1).addFocusables(views, direction);
+ }
+ } else if (direction == View.FOCUS_RIGHT){
+ if (mCurrentPage < getPageCount() - 1) {
+ getPageAt(mCurrentPage + 1).addFocusables(views, direction);
+ }
+ }
+ }
+
+ /**
+ * If one of our descendant views decides that it could be focused now, only
+ * pass that along if it's on the current page.
+ *
+ * This happens when live folders requery, and if they're off page, they
+ * end up calling requestFocus, which pulls it on page.
+ */
+ @Override
+ public void focusableViewAvailable(View focused) {
+ View current = getPageAt(mCurrentPage);
+ View v = focused;
+ while (true) {
+ if (v == current) {
+ super.focusableViewAvailable(focused);
+ return;
+ }
+ if (v == this) {
+ return;
+ }
+ ViewParent parent = v.getParent();
+ if (parent instanceof View) {
+ v = (View)v.getParent();
+ } else {
+ return;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ if (disallowIntercept) {
+ // We need to make sure to cancel our long press if
+ // a scrollable widget takes over touch events
+ final View currentPage = getChildAt(mCurrentPage);
+ currentPage.cancelLongPress();
+ }
+ super.requestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ /*
+ * This method JUST determines whether we want to intercept the motion.
+ * If we return true, onTouchEvent will be called and we do the actual
+ * scrolling there.
+ */
+
+ /*
+ * Shortcut the most recurring case: the user is in the dragging
+ * state and he is moving his finger. We want to intercept this
+ * motion.
+ */
+ final int action = ev.getAction();
+ if ((action == MotionEvent.ACTION_MOVE) &&
+ (mTouchState == TOUCH_STATE_SCROLLING)) {
+ return true;
+ }
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_MOVE: {
+ /*
+ * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+ * whether the user has moved far enough from his original down touch.
+ */
+ if (mActivePointerId != INVALID_POINTER) {
+ determineScrollingStart(ev);
+ break;
+ }
+ // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
+ // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
+ // i.e. fall through to the next case (don't break)
+ // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
+ // while it's small- this was causing a crash before we checked for INVALID_POINTER)
+ }
+
+ case MotionEvent.ACTION_DOWN: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+ // Remember location of down touch
+ mDownMotionX = x;
+ mLastMotionX = x;
+ mLastMotionY = y;
+ mActivePointerId = ev.getPointerId(0);
+ mAllowLongPress = true;
+
+ /*
+ * If being flinged and user touches the screen, initiate drag;
+ * otherwise don't. mScroller.isFinished should be false when
+ * being flinged.
+ */
+ final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
+ final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
+ if (finishedScrolling) {
+ mTouchState = TOUCH_STATE_REST;
+ mScroller.abortAnimation();
+ } else {
+ mTouchState = TOUCH_STATE_SCROLLING;
+ }
+
+ // check if this can be the beginning of a tap on the side of the pages
+ // to scroll the current page
+ if ((mTouchState != TOUCH_STATE_PREV_PAGE) && !handlePagingClicks() &&
+ (mTouchState != TOUCH_STATE_NEXT_PAGE)) {
+ if (getChildCount() > 0) {
+ int width = getMeasuredWidth();
+ int offset = getRelativeChildOffset(mCurrentPage);
+ if (x < offset - mPageSpacing) {
+ mTouchState = TOUCH_STATE_PREV_PAGE;
+ } else if (x > (width - offset + mPageSpacing)) {
+ mTouchState = TOUCH_STATE_NEXT_PAGE;
+ }
+ }
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mTouchState = TOUCH_STATE_REST;
+ mAllowLongPress = false;
+ mActivePointerId = INVALID_POINTER;
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ break;
+ }
+
+ /*
+ * The only time we want to intercept motion events is if we are in the
+ * drag mode.
+ */
+ return mTouchState != TOUCH_STATE_REST;
+ }
+
+ protected void animateClickFeedback(View v, final Runnable r) {
+ // animate the view slightly to show click feedback running some logic after it is "pressed"
+ Animation anim = AnimationUtils.loadAnimation(getContext(),
+ R.anim.paged_view_click_feedback);
+ anim.setAnimationListener(new AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {}
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ r.run();
+ }
+ @Override
+ public void onAnimationEnd(Animation animation) {}
+ });
+ v.startAnimation(anim);
+ }
+
+ /*
+ * Determines if we should change the touch state to start scrolling after the
+ * user moves their touch point too far.
+ */
+ protected void determineScrollingStart(MotionEvent ev) {
+ /*
+ * Locally do absolute value. mLastMotionX is set to the y value
+ * of the down event.
+ */
+ final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ final float x = ev.getX(pointerIndex);
+ final float y = ev.getY(pointerIndex);
+ final int xDiff = (int) Math.abs(x - mLastMotionX);
+ final int yDiff = (int) Math.abs(y - mLastMotionY);
+
+ final int touchSlop = mTouchSlop;
+ boolean xPaged = xDiff > mPagingTouchSlop;
+ boolean xMoved = xDiff > touchSlop;
+ boolean yMoved = yDiff > touchSlop;
+
+ if (xMoved || yMoved) {
+ if (mUsePagingTouchSlop ? xPaged : xMoved) {
+ // Scroll if the user moved far enough along the X axis
+ mTouchState = TOUCH_STATE_SCROLLING;
+ mLastMotionX = x;
+ mTouchX = mScrollX;
+ mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+ pageBeginMoving();
+ }
+ // Either way, cancel any pending longpress
+ if (mAllowLongPress) {
+ mAllowLongPress = false;
+ // Try canceling the long press. It could also have been scheduled
+ // by a distant descendant, so use the mAllowLongPress flag to block
+ // everything
+ final View currentPage = getPageAt(mCurrentPage);
+ if (currentPage != null) {
+ currentPage.cancelLongPress();
+ }
+ }
+ }
+ }
+
+ protected boolean handlePagingClicks() {
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ acquireVelocityTrackerAndAddMovement(ev);
+
+ final int action = ev.getAction();
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ /*
+ * If being flinged and user touches, stop the fling. isFinished
+ * will be false if being flinged.
+ */
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+
+ // Remember where the motion event started
+ mDownMotionX = mLastMotionX = ev.getX();
+ mActivePointerId = ev.getPointerId(0);
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ pageBeginMoving();
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ // Scroll to follow the motion event
+ final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ final float x = ev.getX(pointerIndex);
+ final int deltaX = (int) (mLastMotionX - x);
+ mLastMotionX = x;
+
+ int sx = getScrollX();
+ if (deltaX < 0) {
+ if (sx > 0) {
+ mTouchX += Math.max(-mTouchX, deltaX);
+ mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+ if (!mDeferScrollUpdate) {
+ scrollBy(Math.max(-sx, deltaX), 0);
+ } else {
+ // This will trigger a call to computeScroll() on next drawChild() call
+ invalidate();
+ }
+ }
+ } else if (deltaX > 0) {
+ final int lastChildIndex = getChildCount() - 1;
+ final int availableToScroll = getChildOffset(lastChildIndex) -
+ getRelativeChildOffset(lastChildIndex) - sx;
+ if (availableToScroll > 0) {
+ mTouchX += Math.min(availableToScroll, deltaX);
+ mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+ if (!mDeferScrollUpdate) {
+ scrollBy(Math.min(availableToScroll, deltaX), 0);
+ } else {
+ // This will trigger a call to computeScroll() on next drawChild() call
+ invalidate();
+ }
+ }
+ } else {
+ awakenScrollBars();
+ }
+ } else {
+ determineScrollingStart(ev);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ final int activePointerId = mActivePointerId;
+ final int pointerIndex = ev.findPointerIndex(activePointerId);
+ final float x = ev.getX(pointerIndex);
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
+ final int deltaX = (int) (x - mDownMotionX);
+ boolean isfling = Math.abs(deltaX) > MIN_LENGTH_FOR_FLING;
+ boolean isSignificantMove = Math.abs(deltaX) > MIN_LENGTH_FOR_MOVE;
+
+ final int snapVelocity = mSnapVelocity;
+ if ((isSignificantMove && deltaX > 0 ||
+ (isfling && velocityX > snapVelocity)) &&
+ mCurrentPage > 0) {
+ snapToPageWithVelocity(mCurrentPage - 1, velocityX);
+ } else if ((isSignificantMove && deltaX < 0 ||
+ (isfling && velocityX < -snapVelocity)) &&
+ mCurrentPage < getChildCount() - 1) {
+ snapToPageWithVelocity(mCurrentPage + 1, velocityX);
+ } else {
+ snapToDestination();
+ }
+ } else if (mTouchState == TOUCH_STATE_PREV_PAGE && !handlePagingClicks()) {
+ // at this point we have not moved beyond the touch slop
+ // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
+ // we can just page
+ int nextPage = Math.max(0, mCurrentPage - 1);
+ if (nextPage != mCurrentPage) {
+ snapToPage(nextPage);
+ } else {
+ snapToDestination();
+ }
+ } else if (mTouchState == TOUCH_STATE_NEXT_PAGE && !handlePagingClicks()) {
+ // at this point we have not moved beyond the touch slop
+ // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
+ // we can just page
+ int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
+ if (nextPage != mCurrentPage) {
+ snapToPage(nextPage);
+ } else {
+ snapToDestination();
+ }
+ }
+ mTouchState = TOUCH_STATE_REST;
+ mActivePointerId = INVALID_POINTER;
+ releaseVelocityTracker();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ snapToDestination();
+ }
+ mTouchState = TOUCH_STATE_REST;
+ mActivePointerId = INVALID_POINTER;
+ releaseVelocityTracker();
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ break;
+ }
+
+ return true;
+ }
+
+ private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+ }
+
+ private void releaseVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private void onSecondaryPointerUp(MotionEvent ev) {
+ final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
+ MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ // TODO: Make this decision more intelligent.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
+ mLastMotionY = ev.getY(newPointerIndex);
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ }
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ super.requestChildFocus(child, focused);
+ int page = indexOfChild(child);
+ if (page >= 0 && !isInTouchMode()) {
+ snapToPage(page);
+ }
+ }
+
+ protected int getChildIndexForRelativeOffset(int relativeOffset) {
+ final int childCount = getChildCount();
+ int left;
+ int right;
+ for (int i = 0; i < childCount; ++i) {
+ left = getRelativeChildOffset(i);
+ right = (left + getChildAt(i).getMeasuredWidth());
+ if (left <= relativeOffset && relativeOffset <= right) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ protected int getRelativeChildOffset(int index) {
+ return (getMeasuredWidth() - getChildAt(index).getMeasuredWidth()) / 2;
+ }
+
+ protected int getChildOffset(int index) {
+ if (getChildCount() == 0)
+ return 0;
+
+ int offset = getRelativeChildOffset(0);
+ for (int i = 0; i < index; ++i) {
+ offset += getChildAt(i).getMeasuredWidth() + mPageSpacing;
+ }
+ return offset;
+ }
+
+ int getPageNearestToCenterOfScreen() {
+ int minDistanceFromScreenCenter = getMeasuredWidth();
+ int minDistanceFromScreenCenterIndex = -1;
+ int screenCenter = mScrollX + (getMeasuredWidth() / 2);
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; ++i) {
+ View layout = (View) getChildAt(i);
+ int childWidth = layout.getMeasuredWidth();
+ int halfChildWidth = (childWidth / 2);
+ int childCenter = getChildOffset(i) + halfChildWidth;
+ int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
+ if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
+ minDistanceFromScreenCenter = distanceFromScreenCenter;
+ minDistanceFromScreenCenterIndex = i;
+ }
+ }
+ return minDistanceFromScreenCenterIndex;
+ }
+
+ protected void snapToDestination() {
+ snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
+ }
+
+ protected void snapToPageWithVelocity(int whichPage, int velocity) {
+ // We ignore velocity in this implementation, but children (e.g. SmoothPagedView)
+ // can use it
+ snapToPage(whichPage);
+ }
+
+ protected void snapToPage(int whichPage) {
+ snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+ }
+
+ protected void snapToPage(int whichPage, int duration) {
+ whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
+
+ int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
+ final int sX = getScrollX();
+ final int delta = newX - sX;
+ snapToPage(whichPage, delta, duration);
+ }
+
+ protected void snapToPage(int whichPage, int delta, int duration) {
+ mNextPage = whichPage;
+
+ View focusedChild = getFocusedChild();
+ if (focusedChild != null && whichPage != mCurrentPage &&
+ focusedChild == getChildAt(mCurrentPage)) {
+ focusedChild.clearFocus();
+ }
+
+ pageBeginMoving();
+ awakenScrollBars(duration);
+ if (duration == 0) {
+ duration = Math.abs(delta);
+ }
+
+ if (!mScroller.isFinished()) mScroller.abortAnimation();
+ mScroller.startScroll(getScrollX(), 0, delta, 0, duration);
+
+ // only load some associated pages
+ loadAssociatedPages(mNextPage);
+ notifyPageSwitchListener();
+ invalidate();
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final SavedState state = new SavedState(super.onSaveInstanceState());
+ state.currentPage = mCurrentPage;
+ return state;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ SavedState savedState = (SavedState) state;
+ super.onRestoreInstanceState(savedState.getSuperState());
+ if (savedState.currentPage != -1) {
+ mCurrentPage = savedState.currentPage;
+ }
+ }
+
+ public void scrollLeft() {
+ if (mScroller.isFinished()) {
+ if (mCurrentPage > 0) snapToPage(mCurrentPage - 1);
+ } else {
+ if (mNextPage > 0) snapToPage(mNextPage - 1);
+ }
+ }
+
+ public void scrollRight() {
+ if (mScroller.isFinished()) {
+ if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1);
+ } else {
+ if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1);
+ }
+ }
+
+ public int getPageForView(View v) {
+ int result = -1;
+ if (v != null) {
+ ViewParent vp = v.getParent();
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ if (vp == getChildAt(i)) {
+ return i;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @return True is long presses are still allowed for the current touch
+ */
+ public boolean allowLongPress() {
+ return mAllowLongPress;
+ }
+
+ /**
+ * Set true to allow long-press events to be triggered, usually checked by
+ * {@link Launcher} to accept or block dpad-initiated long-presses.
+ */
+ public void setAllowLongPress(boolean allowLongPress) {
+ mAllowLongPress = allowLongPress;
+ }
+
+ public static class SavedState extends BaseSavedState {
+ int currentPage = -1;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ currentPage = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(currentPage);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ public void loadAssociatedPages(int page) {
+ if (mContentIsRefreshable) {
+ final int count = getChildCount();
+ if (page < count) {
+ int lowerPageBound = getAssociatedLowerPageBound(page);
+ int upperPageBound = getAssociatedUpperPageBound(page);
+ for (int i = 0; i < count; ++i) {
+ final ViewGroup layout = (ViewGroup) getChildAt(i);
+ final int childCount = layout.getChildCount();
+ if (lowerPageBound <= i && i <= upperPageBound) {
+ if (mDirtyPageContent.get(i)) {
+ syncPageItems(i);
+ mDirtyPageContent.set(i, false);
+ }
+ } else {
+ if (childCount > 0) {
+ layout.removeAllViews();
+ }
+ mDirtyPageContent.set(i, true);
+ }
+ }
+ }
+ }
+ }
+
+ protected int getAssociatedLowerPageBound(int page) {
+ return Math.max(0, page - 1);
+ }
+ protected int getAssociatedUpperPageBound(int page) {
+ final int count = getChildCount();
+ return Math.min(page + 1, count - 1);
+ }
+
+ protected void startChoiceMode(int mode, ActionMode.Callback callback) {
+ if (isChoiceMode(CHOICE_MODE_NONE)) {
+ mChoiceMode = mode;
+ mActionMode = startActionMode(callback);
+ }
+ }
+
+ public void endChoiceMode() {
+ if (!isChoiceMode(CHOICE_MODE_NONE)) {
+ mChoiceMode = CHOICE_MODE_NONE;
+ resetCheckedGrandchildren();
+ if (mActionMode != null) mActionMode.finish();
+ mActionMode = null;
+ }
+ }
+
+ protected boolean isChoiceMode(int mode) {
+ return mChoiceMode == mode;
+ }
+
+ protected ArrayList<Checkable> getCheckedGrandchildren() {
+ ArrayList<Checkable> checked = new ArrayList<Checkable>();
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; ++i) {
+ final ViewGroup layout = (ViewGroup) getChildAt(i);
+ final int grandChildCount = layout.getChildCount();
+ for (int j = 0; j < grandChildCount; ++j) {
+ final View v = layout.getChildAt(j);
+ if (v instanceof Checkable && ((Checkable) v).isChecked()) {
+ checked.add((Checkable) v);
+ }
+ }
+ }
+ return checked;
+ }
+
+ /**
+ * If in CHOICE_MODE_SINGLE and an item is checked, returns that item.
+ * Otherwise, returns null.
+ */
+ protected Checkable getSingleCheckedGrandchild() {
+ if (mChoiceMode == CHOICE_MODE_SINGLE) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; ++i) {
+ final ViewGroup layout = (ViewGroup) getChildAt(i);
+ final int grandChildCount = layout.getChildCount();
+ for (int j = 0; j < grandChildCount; ++j) {
+ final View v = layout.getChildAt(j);
+ if (v instanceof Checkable && ((Checkable) v).isChecked()) {
+ return (Checkable) v;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public Object getChosenItem() {
+ View checkedView = (View) getSingleCheckedGrandchild();
+ if (checkedView != null) {
+ return checkedView.getTag();
+ }
+ return null;
+ }
+
+ protected void resetCheckedGrandchildren() {
+ // loop through children, and set all of their children to _not_ be checked
+ final ArrayList<Checkable> checked = getCheckedGrandchildren();
+ for (int i = 0; i < checked.size(); ++i) {
+ final Checkable c = checked.get(i);
+ c.setChecked(false);
+ }
+ }
+
+ /**
+ * This method is called ONLY to synchronize the number of pages that the paged view has.
+ * To actually fill the pages with information, implement syncPageItems() below. It is
+ * guaranteed that syncPageItems() will be called for a particular page before it is shown,
+ * and therefore, individual page items do not need to be updated in this method.
+ */
+ public abstract void syncPages();
+
+ /**
+ * This method is called to synchronize the items that are on a particular page. If views on
+ * the page can be reused, then they should be updated within this method.
+ */
+ public abstract void syncPageItems(int page);
+
+ public void invalidatePageData() {
+ if (mContentIsRefreshable) {
+ // Update all the pages
+ syncPages();
+
+ // Mark each of the pages as dirty
+ final int count = getChildCount();
+ mDirtyPageContent.clear();
+ for (int i = 0; i < count; ++i) {
+ mDirtyPageContent.add(true);
+ }
+
+ // Load any pages that are necessary for the current window of views
+ loadAssociatedPages(mCurrentPage);
+ mDirtyPageAlpha = true;
+ updateAdjacentPagesAlpha();
+ requestLayout();
+ }
+ }
+}
diff --git a/src/com/android/launcher2/PagedViewCellLayout.java b/src/com/android/launcher2/PagedViewCellLayout.java
new file mode 100644
index 000000000..b779a973d
--- /dev/null
+++ b/src/com/android/launcher2/PagedViewCellLayout.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+
+/**
+ * An abstraction of the original CellLayout which supports laying out items
+ * which span multiple cells into a grid-like layout. Also supports dimming
+ * to give a preview of its contents.
+ */
+public class PagedViewCellLayout extends ViewGroup {
+ static final String TAG = "PagedViewCellLayout";
+
+ private float mHolographicAlpha;
+
+ private boolean mCenterContent;
+
+ private int mCellCountX;
+ private int mCellCountY;
+ private int mCellWidth;
+ private int mCellHeight;
+ private int mWidthGap;
+ private int mHeightGap;
+ private static int sDefaultCellDimensions = 96;
+
+ public PagedViewCellLayout(Context context) {
+ this(context, null);
+ }
+
+ public PagedViewCellLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PagedViewCellLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ setAlwaysDrawnWithCacheEnabled(false);
+
+ // setup default cell parameters
+ mCellWidth = mCellHeight = sDefaultCellDimensions;
+ mCellCountX = LauncherModel.getCellCountX();
+ mCellCountY = LauncherModel.getCellCountY();
+ mHolographicAlpha = 0.0f;
+ mWidthGap = mHeightGap = -1;
+ }
+
+ @Override
+ protected boolean onSetAlpha(int alpha) {
+ return true;
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ mHolographicAlpha = 1.0f - alpha;
+ setChildrenAlpha(alpha);
+ super.setAlpha(alpha);
+ }
+
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+
+ // Cancel long press for all children
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.cancelLongPress();
+ }
+ }
+
+ public boolean addViewToCellLayout(View child, int index, int childId,
+ PagedViewCellLayout.LayoutParams params) {
+ final PagedViewCellLayout.LayoutParams lp = params;
+
+ // Generate an id for each view, this assumes we have at most 256x256 cells
+ // per workspace screen
+ if (lp.cellX >= 0 && lp.cellX <= (mCellCountX - 1) &&
+ lp.cellY >= 0 && (lp.cellY <= mCellCountY - 1)) {
+ // If the horizontal or vertical span is set to -1, it is taken to
+ // mean that it spans the extent of the CellLayout
+ if (lp.cellHSpan < 0) lp.cellHSpan = mCellCountX;
+ if (lp.cellVSpan < 0) lp.cellVSpan = mCellCountY;
+
+ child.setId(childId);
+
+ // We might be in the middle or end of shrinking/fading to a dimmed view
+ // Make sure this view's alpha is set the same as all the rest of the views
+ child.setAlpha(1.0f - mHolographicAlpha);
+
+ addView(child, index, lp);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ super.requestChildFocus(child, focused);
+ if (child != null) {
+ Rect r = new Rect();
+ child.getDrawingRect(r);
+ requestRectangleOnScreen(r);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // TODO: currently ignoring padding
+
+ int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+
+ int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
+ throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
+ }
+
+ final int cellWidth = mCellWidth;
+ final int cellHeight = mCellHeight;
+
+ int numWidthGaps = mCellCountX - 1;
+ int numHeightGaps = mCellCountY - 1;
+
+ int vSpaceLeft = heightSpecSize - mPaddingTop
+ - mPaddingBottom - (cellHeight * mCellCountY);
+ int heightGap = vSpaceLeft / numHeightGaps;
+
+ int hSpaceLeft = widthSpecSize - mPaddingLeft
+ - mPaddingRight - (cellWidth * mCellCountX);
+ int widthGap = hSpaceLeft / numWidthGaps;
+
+ // center it around the min gaps
+ int minGap = Math.min(widthGap, heightGap);
+ int paddingLeft = mPaddingLeft;
+ int paddingTop = mPaddingTop;
+ /*
+ if (minGap < heightGap) {
+ // vertical space has shrunken, so change padding accordingly
+ paddingTop += ((heightGap - minGap) * (mCellCountY - 1)) / 2;
+ } else if (minGap < widthGap) {
+ // horizontal space has shrunken, so change padding accordingly
+ paddingLeft += ((widthGap - minGap) * (mCellCountX - 1)) / 2;
+ }
+ */
+ if (mWidthGap > -1 && mHeightGap > -1) {
+ widthGap = mWidthGap;
+ heightGap = mHeightGap;
+ } else {
+ widthGap = heightGap = minGap;
+ }
+
+ int newWidth = mPaddingLeft + mPaddingRight + (mCellCountX * cellWidth) +
+ ((mCellCountX - 1) * widthGap);
+ int newHeight = mPaddingTop + mPaddingBottom + (mCellCountY * cellHeight) +
+ ((mCellCountY - 1) * heightGap);
+
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ PagedViewCellLayout.LayoutParams lp =
+ (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
+ lp.setup(cellWidth, cellHeight, widthGap, heightGap,
+ paddingLeft, paddingTop);
+
+ int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
+ MeasureSpec.EXACTLY);
+ int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
+ MeasureSpec.EXACTLY);
+
+ child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+ }
+
+ setMeasuredDimension(newWidth, newHeight);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int count = getChildCount();
+
+ int offsetX = 0;
+ if (mCenterContent) {
+ // determine the max width of all the rows and center accordingly
+ int maxRowWidth = 0;
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ PagedViewCellLayout.LayoutParams lp =
+ (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
+ maxRowWidth = Math.max(maxRowWidth, lp.x + lp.width);
+ }
+ }
+ offsetX = (getMeasuredWidth() / 2) - (maxRowWidth / 2);
+ }
+
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ PagedViewCellLayout.LayoutParams lp =
+ (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
+
+ int childLeft = offsetX + lp.x;
+ int childTop = lp.y;
+ child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
+ }
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return super.onTouchEvent(event) || true;
+ }
+
+ public void enableCenteredContent(boolean enabled) {
+ mCenterContent = enabled;
+ }
+
+ @Override
+ protected void setChildrenDrawingCacheEnabled(boolean enabled) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View view = getChildAt(i);
+ view.setDrawingCacheEnabled(enabled);
+ // Update the drawing caches
+ if (!view.isHardwareAccelerated()) {
+ view.buildDrawingCache(true);
+ }
+ }
+ }
+
+ public void setCellCount(int xCount, int yCount) {
+ mCellCountX = xCount;
+ mCellCountY = yCount;
+ requestLayout();
+ }
+
+ public void setGap(int widthGap, int heightGap) {
+ mWidthGap = widthGap;
+ mHeightGap = heightGap;
+ }
+
+ public void setCellDimensions(int width, int height) {
+ mCellWidth = width;
+ mCellHeight = height;
+ requestLayout();
+ }
+
+ public int getDefaultCellDimensions() {
+ return sDefaultCellDimensions;
+ }
+
+ private void setChildrenAlpha(float alpha) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ getChildAt(i).setAlpha(alpha);
+ }
+ }
+
+ public int[] getCellCountForDimensions(int width, int height) {
+ // Always assume we're working with the smallest span to make sure we
+ // reserve enough space in both orientations
+ int smallerSize = Math.min(mCellWidth, mCellHeight);
+
+ // Always round up to next largest cell
+ int spanX = (width + smallerSize) / smallerSize;
+ int spanY = (height + smallerSize) / smallerSize;
+
+ return new int[] { spanX, spanY };
+ }
+
+ /**
+ * Start dragging the specified child
+ *
+ * @param child The child that is being dragged
+ */
+ void onDragChild(View child) {
+ PagedViewCellLayout.LayoutParams lp = (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
+ lp.isDragging = true;
+ }
+
+ /**
+ * Estimates the number of cells that the specified width would take up.
+ */
+ public int estimateCellHSpan(int width) {
+ // TODO: we need to take widthGap into effect
+ return (width + mCellWidth) / mCellWidth;
+ }
+
+ /**
+ * Estimates the number of cells that the specified height would take up.
+ */
+ public int estimateCellVSpan(int height) {
+ // TODO: we need to take heightGap into effect
+ return (height + mCellHeight) / mCellHeight;
+ }
+
+ /**
+ * Estimates the width that the number of vSpan cells will take up.
+ */
+ public int estimateCellWidth(int hSpan) {
+ // TODO: we need to take widthGap into effect
+ return hSpan * mCellWidth;
+ }
+
+ /**
+ * Estimates the height that the number of vSpan cells will take up.
+ */
+ public int estimateCellHeight(int vSpan) {
+ // TODO: we need to take heightGap into effect
+ return vSpan * mCellHeight;
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new PagedViewCellLayout.LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof PagedViewCellLayout.LayoutParams;
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new PagedViewCellLayout.LayoutParams(p);
+ }
+
+ public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+ /**
+ * Horizontal location of the item in the grid.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellX;
+
+ /**
+ * Vertical location of the item in the grid.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellY;
+
+ /**
+ * Number of cells spanned horizontally by the item.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellHSpan;
+
+ /**
+ * Number of cells spanned vertically by the item.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellVSpan;
+
+ /**
+ * Is this item currently being dragged
+ */
+ public boolean isDragging;
+
+ // a data object that you can bind to this layout params
+ private Object mTag;
+
+ // X coordinate of the view in the layout.
+ @ViewDebug.ExportedProperty
+ int x;
+ // Y coordinate of the view in the layout.
+ @ViewDebug.ExportedProperty
+ int y;
+
+ public LayoutParams() {
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ cellHSpan = 1;
+ cellVSpan = 1;
+ }
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ cellHSpan = 1;
+ cellVSpan = 1;
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ cellHSpan = 1;
+ cellVSpan = 1;
+ }
+
+ public LayoutParams(LayoutParams source) {
+ super(source);
+ this.cellX = source.cellX;
+ this.cellY = source.cellY;
+ this.cellHSpan = source.cellHSpan;
+ this.cellVSpan = source.cellVSpan;
+ }
+
+ public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ this.cellX = cellX;
+ this.cellY = cellY;
+ this.cellHSpan = cellHSpan;
+ this.cellVSpan = cellVSpan;
+ }
+
+ public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
+ int hStartPadding, int vStartPadding) {
+
+ final int myCellHSpan = cellHSpan;
+ final int myCellVSpan = cellVSpan;
+ final int myCellX = cellX;
+ final int myCellY = cellY;
+
+ width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
+ leftMargin - rightMargin;
+ height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
+ topMargin - bottomMargin;
+
+ x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
+ y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
+ }
+
+ public Object getTag() {
+ return mTag;
+ }
+
+ public void setTag(Object tag) {
+ mTag = tag;
+ }
+
+ public String toString() {
+ return "(" + this.cellX + ", " + this.cellY + ", " +
+ this.cellHSpan + ", " + this.cellVSpan + ")";
+ }
+ }
+}
diff --git a/src/com/android/launcher2/PagedViewIcon.java b/src/com/android/launcher2/PagedViewIcon.java
new file mode 100644
index 000000000..89cf331dd
--- /dev/null
+++ b/src/com/android/launcher2/PagedViewIcon.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import com.android.launcher.R;
+import com.android.launcher2.PagedView.PagedViewIconCache;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.widget.Checkable;
+import android.widget.TextView;
+
+
+
+/**
+ * An icon on a PagedView, specifically for items in the launcher's paged view (with compound
+ * drawables on the top).
+ */
+public class PagedViewIcon extends TextView implements Checkable {
+ private static final String TAG = "PagedViewIcon";
+
+ // holographic outline
+ private final Paint mPaint = new Paint();
+ private static HolographicOutlineHelper sHolographicOutlineHelper;
+ private Bitmap mCheckedOutline;
+ private Bitmap mHolographicOutline;
+ private Bitmap mIcon;
+
+ private Object mIconCacheKey;
+ private PagedViewIconCache mIconCache;
+
+ private int mAlpha;
+ private int mHolographicAlpha;
+
+ private boolean mIsChecked;
+
+ // Highlight colors
+ private int mHoloBlurColor;
+ private int mHoloOutlineColor;
+ private int mCheckedBlurColor;
+ private int mCheckedOutlineColor;
+
+ private static final HandlerThread sWorkerThread = new HandlerThread("pagedviewicon-helper");
+ static {
+ sWorkerThread.start();
+ }
+
+ private static final int MESSAGE_CREATE_HOLOGRAPHIC_OUTLINE = 1;
+
+ private static final Handler sWorker = new Handler(sWorkerThread.getLooper()) {
+ private DeferredHandler mHandler = new DeferredHandler();
+ private Paint mPaint = new Paint();
+ public void handleMessage(Message msg) {
+ final PagedViewIcon icon = (PagedViewIcon) msg.obj;
+
+ final Bitmap holographicOutline = Bitmap.createBitmap(
+ icon.mIcon.getWidth(), icon.mIcon.getHeight(), Bitmap.Config.ARGB_8888);
+ Canvas holographicOutlineCanvas = new Canvas(holographicOutline);
+ holographicOutlineCanvas.drawBitmap(icon.mIcon, 0, 0, mPaint);
+
+ sHolographicOutlineHelper.applyExpensiveOutlineWithBlur(holographicOutline,
+ holographicOutlineCanvas, icon.mHoloBlurColor, icon.mHoloOutlineColor);
+
+ mHandler.post(new Runnable() {
+ public void run() {
+ icon.mHolographicOutline = holographicOutline;
+ icon.mIconCache.addOutline(icon.mIconCacheKey, holographicOutline);
+ icon.invalidate();
+ }
+ });
+ }
+ };
+
+ public PagedViewIcon(Context context) {
+ this(context, null);
+ }
+
+ public PagedViewIcon(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PagedViewIcon(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedViewIcon, defStyle, 0);
+ mHoloBlurColor = a.getColor(R.styleable.PagedViewIcon_blurColor, 0);
+ mHoloOutlineColor = a.getColor(R.styleable.PagedViewIcon_outlineColor, 0);
+ mCheckedBlurColor = a.getColor(R.styleable.PagedViewIcon_checkedBlurColor, 0);
+ mCheckedOutlineColor = a.getColor(R.styleable.PagedViewIcon_checkedOutlineColor, 0);
+
+ a.recycle();
+
+ if (sHolographicOutlineHelper == null) {
+ sHolographicOutlineHelper = new HolographicOutlineHelper();
+ }
+
+ setFocusable(true);
+ setBackgroundDrawable(null);
+ }
+
+ private void queueHolographicOutlineCreation() {
+ // Generate the outline in the background
+ if (mHolographicOutline == null) {
+ Message m = sWorker.obtainMessage(MESSAGE_CREATE_HOLOGRAPHIC_OUTLINE);
+ m.obj = this;
+ sWorker.sendMessage(m);
+ }
+ }
+
+ public void applyFromApplicationInfo(ApplicationInfo info, PagedViewIconCache cache,
+ boolean scaleUp) {
+ mIconCache = cache;
+ mIconCacheKey = info;
+ mHolographicOutline = mIconCache.getOutline(mIconCacheKey);
+
+ mIcon = info.iconBitmap;
+ setCompoundDrawablesWithIntrinsicBounds(null, new FastBitmapDrawable(mIcon), null, null);
+ setText(info.title);
+ setTag(info);
+
+ queueHolographicOutlineCreation();
+ }
+
+ public void applyFromResolveInfo(ResolveInfo info, PackageManager packageManager,
+ PagedViewIconCache cache, IconCache modelIconCache) {
+ mIconCache = cache;
+ mIconCacheKey = info;
+ mHolographicOutline = mIconCache.getOutline(mIconCacheKey);
+
+ mIcon = Utilities.createIconBitmap(
+ modelIconCache.getFullResIcon(info, packageManager), mContext);
+ setCompoundDrawablesWithIntrinsicBounds(null, new FastBitmapDrawable(mIcon), null, null);
+ setText(info.loadLabel(packageManager));
+ setTag(info);
+
+ queueHolographicOutlineCreation();
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ final float viewAlpha = sHolographicOutlineHelper.viewAlphaInterpolator(alpha);
+ final float holographicAlpha = sHolographicOutlineHelper.highlightAlphaInterpolator(alpha);
+ mAlpha = (int) (viewAlpha * 255);
+ mHolographicAlpha = (int) (holographicAlpha * 255);
+ super.setAlpha(viewAlpha);
+ }
+
+ public void invalidateCheckedImage() {
+ if (mCheckedOutline != null) {
+ mCheckedOutline.recycle();
+ mCheckedOutline = null;
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mAlpha > 0) {
+ super.onDraw(canvas);
+ }
+
+ Bitmap overlay = null;
+
+ // draw any blended overlays
+ if (mCheckedOutline == null) {
+ if (mHolographicOutline != null && mHolographicAlpha > 0) {
+ mPaint.setAlpha(mHolographicAlpha);
+ overlay = mHolographicOutline;
+ }
+ } else {
+ mPaint.setAlpha(255);
+ overlay = mCheckedOutline;
+ }
+
+ if (overlay != null) {
+ final int compoundPaddingLeft = getCompoundPaddingLeft();
+ final int compoundPaddingRight = getCompoundPaddingRight();
+ int hspace = getWidth() - compoundPaddingRight - compoundPaddingLeft;
+ canvas.drawBitmap(overlay,
+ compoundPaddingLeft + (hspace - overlay.getWidth()) / 2,
+ mPaddingTop,
+ mPaint);
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ sWorker.removeMessages(MESSAGE_CREATE_HOLOGRAPHIC_OUTLINE, this);
+ }
+
+ @Override
+ public boolean isChecked() {
+ return mIsChecked;
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ if (mIsChecked != checked) {
+ mIsChecked = checked;
+
+ if (mIsChecked) {
+ mCheckedOutline = Bitmap.createBitmap(mIcon.getWidth(), mIcon.getHeight(),
+ Bitmap.Config.ARGB_8888);
+ Canvas checkedOutlineCanvas = new Canvas(mCheckedOutline);
+ mPaint.setAlpha(255);
+ checkedOutlineCanvas.drawBitmap(mIcon, 0, 0, mPaint);
+
+ sHolographicOutlineHelper.applyExpensiveOutlineWithBlur(mCheckedOutline,
+ checkedOutlineCanvas, mCheckedBlurColor, mCheckedOutlineColor);
+ } else {
+ invalidateCheckedImage();
+ }
+
+ invalidate();
+ }
+ }
+
+ @Override
+ public void toggle() {
+ setChecked(!mIsChecked);
+ }
+}
diff --git a/src/com/android/launcher2/PagedViewWidgetIcon.java b/src/com/android/launcher2/PagedViewWidgetIcon.java
new file mode 100644
index 000000000..f285dab7f
--- /dev/null
+++ b/src/com/android/launcher2/PagedViewWidgetIcon.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Checkable;
+import android.widget.LinearLayout;
+
+import com.android.launcher.R;
+
+/**
+ * An widget icon for use specifically in the CustomizePagedView. In class form so that
+ * we can add logic for how it will look when checked/unchecked.
+ */
+public class PagedViewWidgetIcon extends LinearLayout implements Checkable {
+ private static final String TAG = "PagedViewIcon";
+
+ // Holographic outline
+ private final Paint mPaint = new Paint();
+ private static HolographicOutlineHelper sHolographicOutlineHelper;
+ private final Paint mErasePaint = new Paint();
+ private Bitmap mCheckedOutline;
+ private Canvas mHolographicOutlineCanvas;
+ private boolean mIsHolographicUpdatePass;
+
+ private int mAlpha;
+
+ private boolean mIsChecked;
+
+ // Highlight colours
+ private int mCheckedBlurColor;
+ private int mCheckedOutlineColor;
+
+
+ public PagedViewWidgetIcon(Context context) {
+ this(context, null);
+ }
+
+ public PagedViewWidgetIcon(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PagedViewWidgetIcon(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedViewWidgetIcon,
+ defStyle, 0);
+ mCheckedBlurColor = a.getColor(R.styleable.PagedViewWidgetIcon_checkedBlurColor, 0);
+ mCheckedOutlineColor = a.getColor(R.styleable.PagedViewWidgetIcon_checkedOutlineColor, 0);
+ mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ mErasePaint.setFilterBitmap(true);
+ a.recycle();
+
+ if (sHolographicOutlineHelper == null) {
+ sHolographicOutlineHelper = new HolographicOutlineHelper();
+ }
+
+ setWillNotDraw(false);
+ }
+
+ public void invalidateCheckedImage() {
+ if (mCheckedOutline != null) {
+ mCheckedOutline.recycle();
+ mCheckedOutline = null;
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ // Draw the view itself
+ if (mIsHolographicUpdatePass) {
+ canvas.save();
+ final float alpha = getAlpha();
+ super.setAlpha(1.0f);
+ super.onDraw(canvas);
+ super.setAlpha(alpha);
+ canvas.restore();
+ } else {
+ if (mAlpha > 0) {
+ super.onDraw(canvas);
+ }
+ }
+
+ // Draw the holographic checked overlay if necessary
+ if (!mIsHolographicUpdatePass) {
+ if (mCheckedOutline != null) {
+ mPaint.setAlpha(255);
+ canvas.drawBitmap(mCheckedOutline, 0, 0, mPaint);
+ }
+ }
+ }
+
+ @Override
+ public boolean isChecked() {
+ return mIsChecked;
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ if (mIsChecked != checked) {
+ mIsChecked = checked;
+
+ if (mIsChecked) {
+ // set a flag to indicate that we are going to draw the view at full alpha
+ mIsHolographicUpdatePass = true;
+ final int width = getMeasuredWidth();
+ final int height = getMeasuredHeight();
+ mCheckedOutline = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ mHolographicOutlineCanvas = new Canvas(mCheckedOutline);
+ mHolographicOutlineCanvas.concat(getMatrix());
+ draw(mHolographicOutlineCanvas);
+ sHolographicOutlineHelper.applyExpensiveOutlineWithBlur(mCheckedOutline,
+ mHolographicOutlineCanvas, mCheckedBlurColor, mCheckedOutlineColor);
+
+ // Unlike PagedViewIcon, we can't seem to properly set the clip rect for all the
+ // children to respect when drawing... so for now, we erase over those parts in the
+ // checked highlight image
+ mHolographicOutlineCanvas.drawRect(0, findViewById(R.id.divider).getTop(),
+ width, height, mErasePaint);
+
+ mIsHolographicUpdatePass = false;
+ mHolographicOutlineCanvas = null;
+ } else {
+ invalidateCheckedImage();
+ }
+
+ invalidate();
+ }
+ }
+
+ @Override
+ public void toggle() {
+ setChecked(!mIsChecked);
+ }
+}
diff --git a/src/com/android/launcher2/PagedViewWidgetLayout.java b/src/com/android/launcher2/PagedViewWidgetLayout.java
new file mode 100644
index 000000000..4666873ce
--- /dev/null
+++ b/src/com/android/launcher2/PagedViewWidgetLayout.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.LinearLayout;
+
+/**
+ * The linear layout used strictly for the widget tab of the customization tray
+ */
+public class PagedViewWidgetLayout extends LinearLayout {
+ static final String TAG = "PagedViewWidgetLayout";
+
+ public PagedViewWidgetLayout(Context context) {
+ this(context, null);
+ }
+
+ public PagedViewWidgetLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PagedViewWidgetLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // We eat up the touch events here, since the PagedView (which uses the same swiping
+ // touch code as Workspace previously) uses onInterceptTouchEvent() to determine when
+ // the user is scrolling between pages. This means that if the pages themselves don't
+ // handle touch events, it gets forwarded up to PagedView itself, and it's own
+ // onTouchEvent() handling will prevent further intercept touch events from being called
+ // (it's the same view in that case). This is not ideal, but to prevent more changes,
+ // we just always mark the touch event as handled.
+ return super.onTouchEvent(event) || true;
+ }
+
+ @Override
+ protected boolean onSetAlpha(int alpha) {
+ return true;
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ setChildrenAlpha(alpha);
+ super.setAlpha(alpha);
+ }
+
+ private void setChildrenAlpha(float alpha) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ getChildAt(i).setAlpha(alpha);
+ }
+ }
+}
diff --git a/src/com/android/launcher2/PendingAddItemInfo.java b/src/com/android/launcher2/PendingAddItemInfo.java
new file mode 100644
index 000000000..7b564e051
--- /dev/null
+++ b/src/com/android/launcher2/PendingAddItemInfo.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.os.Parcelable;
+
+/**
+ * We pass this object with a drag from the customization tray
+ */
+class PendingAddItemInfo extends ItemInfo {
+ /**
+ * The component that will be created.
+ */
+ ComponentName componentName;
+}
+
+class PendingAddWidgetInfo extends PendingAddItemInfo {
+ int minWidth;
+ int minHeight;
+
+ // Any configuration data that we want to pass to a configuration activity when
+ // starting up a widget
+ String mimeType;
+ Parcelable configurationData;
+
+ public PendingAddWidgetInfo(AppWidgetProviderInfo i, String dataMimeType, Parcelable data) {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+ componentName = i.provider;
+ minWidth = i.minWidth;
+ minHeight = i.minHeight;
+ if (dataMimeType != null && data != null) {
+ mimeType = dataMimeType;
+ configurationData = data;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher2/ShortcutInfo.java b/src/com/android/launcher2/ShortcutInfo.java
index 2c5aec436..72f2d518c 100644
--- a/src/com/android/launcher2/ShortcutInfo.java
+++ b/src/com/android/launcher2/ShortcutInfo.java
@@ -16,16 +16,14 @@
package com.android.launcher2;
+import java.util.ArrayList;
+
import android.content.ComponentName;
import android.content.ContentValues;
-import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
import android.util.Log;
-import java.util.ArrayList;
-
/**
* Represents a launchable icon on the workspaces and in folders.
*/
diff --git a/src/com/android/launcher2/ShortcutsAdapter.java b/src/com/android/launcher2/ShortcutsAdapter.java
index 19c3af0a8..93c500a44 100644
--- a/src/com/android/launcher2/ShortcutsAdapter.java
+++ b/src/com/android/launcher2/ShortcutsAdapter.java
@@ -16,16 +16,15 @@
package com.android.launcher2;
+import java.util.ArrayList;
+
import android.content.Context;
-import android.content.pm.PackageManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
-import java.util.ArrayList;
-
import com.android.launcher.R;
/**
diff --git a/src/com/android/launcher2/SmoothPagedView.java b/src/com/android/launcher2/SmoothPagedView.java
new file mode 100644
index 000000000..56037ffb8
--- /dev/null
+++ b/src/com/android/launcher2/SmoothPagedView.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2008 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.launcher2;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.animation.Interpolator;
+import android.widget.Scroller;
+
+
+public abstract class SmoothPagedView extends PagedView {
+ private static final float SMOOTHING_SPEED = 0.75f;
+ private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED));
+
+ private float mBaseLineFlingVelocity;
+ private float mFlingVelocityInfluence;
+
+ static final int OVERSHOOT_MODE = 0;
+ static final int QUINTIC_MODE = 1;
+
+ int mScrollMode;
+
+ private Interpolator mScrollInterpolator;
+
+ private static class WorkspaceOvershootInterpolator implements Interpolator {
+ private static final float DEFAULT_TENSION = 1.3f;
+ private float mTension;
+
+ public WorkspaceOvershootInterpolator() {
+ mTension = DEFAULT_TENSION;
+ }
+
+ public void setDistance(int distance) {
+ mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
+ }
+
+ public void disableSettle() {
+ mTension = 0.f;
+ }
+
+ public float getInterpolation(float t) {
+ // _o(t) = t * t * ((tension + 1) * t + tension)
+ // o(t) = _o(t - 1) + 1
+ t -= 1.0f;
+ return t * t * ((mTension + 1) * t + mTension) + 1.0f;
+ }
+ }
+
+ private static class QuinticInterpolator implements Interpolator {
+ public QuinticInterpolator() {
+ }
+
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t*t*t*t*t + 1;
+ }
+ }
+
+ /**
+ * Used to inflate the Workspace from XML.
+ *
+ * @param context The application's context.
+ * @param attrs The attributes set containing the Workspace's customization values.
+ */
+ public SmoothPagedView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ /**
+ * Used to inflate the Workspace from XML.
+ *
+ * @param context The application's context.
+ * @param attrs The attributes set containing the Workspace's customization values.
+ * @param defStyle Unused.
+ */
+ public SmoothPagedView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mUsePagingTouchSlop = false;
+
+ // This means that we'll take care of updating the scroll parameter ourselves (we do it
+ // in computeScroll)
+ mDeferScrollUpdate = true;
+ }
+
+ protected int getScrollMode() {
+ return OVERSHOOT_MODE;
+ }
+
+ /**
+ * Initializes various states for this workspace.
+ */
+ @Override
+ protected void init() {
+ super.init();
+
+ mScrollMode = getScrollMode();
+ if (mScrollMode == QUINTIC_MODE) {
+ mBaseLineFlingVelocity = 700.0f;
+ mFlingVelocityInfluence = 0.8f;
+ mScrollInterpolator = new QuinticInterpolator();
+ } else { // QUINTIC_MODE
+ mBaseLineFlingVelocity = 2500.0f;
+ mFlingVelocityInfluence = 0.4f;
+ mScrollInterpolator = new WorkspaceOvershootInterpolator();
+ }
+ mScroller = new Scroller(getContext(), mScrollInterpolator);
+ }
+
+ @Override
+ protected void snapToDestination() {
+ snapToPageWithVelocity(getPageNearestToCenterOfScreen(), 0);
+ }
+
+ @Override
+ protected void snapToPageWithVelocity(int whichPage, int velocity) {
+ snapToPageWithVelocity(whichPage, 0, true);
+ }
+
+ void snapToPageWithVelocity(int whichPage, int velocity, boolean settle) {
+ // if (!mScroller.isFinished()) return;
+
+ whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
+
+ final int screenDelta = Math.max(1, Math.abs(whichPage - mCurrentPage));
+ final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
+ final int delta = newX - mScrollX;
+ int duration;
+ if (mScrollMode == OVERSHOOT_MODE) {
+ duration = (screenDelta + 1) * 100;
+ } else { // QUINTIC_MODE
+ duration = Math.round(Math.abs(delta) * 0.6f);
+ }
+
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+
+ if (mScrollMode == OVERSHOOT_MODE) {
+ if (settle) {
+ ((WorkspaceOvershootInterpolator) mScrollInterpolator).setDistance(screenDelta);
+ } else {
+ ((WorkspaceOvershootInterpolator) mScrollInterpolator).disableSettle();
+ }
+ }
+
+ velocity = Math.abs(velocity);
+ if (velocity > 0) {
+ duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence;
+ } else {
+ duration += 100;
+ }
+
+ snapToPage(whichPage, delta, duration);
+ }
+
+ @Override
+ protected void snapToPage(int whichPage) {
+ snapToPageWithVelocity(whichPage, 0, false);
+ }
+
+ @Override
+ public void computeScroll() {
+ boolean scrollComputed = computeScrollHelper();
+
+ if (!scrollComputed && mTouchState == TOUCH_STATE_SCROLLING) {
+ final float now = System.nanoTime() / NANOTIME_DIV;
+ final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT);
+ final float dx = mTouchX - mScrollX;
+ mScrollX += dx * e;
+ mSmoothingTime = now;
+
+ // Keep generating points as long as we're more than 1px away from the target
+ if (dx > 1.f || dx < -1.f) {
+ invalidate();
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher2/SymmetricalLinearTween.java b/src/com/android/launcher2/SymmetricalLinearTween.java
index 2e0ed8f03..da02242c8 100644
--- a/src/com/android/launcher2/SymmetricalLinearTween.java
+++ b/src/com/android/launcher2/SymmetricalLinearTween.java
@@ -17,9 +17,7 @@
package com.android.launcher2;
import android.os.Handler;
-import android.os.Message;
import android.os.SystemClock;
-import android.util.Log;
/**
* Provides an animation between 0.0f and 1.0f over a given duration.
diff --git a/src/com/android/launcher2/UserFolder.java b/src/com/android/launcher2/UserFolder.java
index d49c27aea..d6799f75e 100644
--- a/src/com/android/launcher2/UserFolder.java
+++ b/src/com/android/launcher2/UserFolder.java
@@ -3,10 +3,8 @@ package com.android.launcher2;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.ArrayAdapter;
import com.android.launcher.R;
@@ -40,11 +38,6 @@ public class UserFolder extends Folder implements DropTarget {
itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT)
&& item.container != mInfo.id;
}
-
- public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset,
- DragView dragView, Object dragInfo, Rect recycle) {
- return null;
- }
public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
DragView dragView, Object dragInfo) {
@@ -79,6 +72,10 @@ public class UserFolder extends Folder implements DropTarget {
}
}
+ public boolean isDropEnabled() {
+ return true;
+ }
+
void bind(FolderInfo info) {
super.bind(info);
setContentAdapter(new ShortcutsAdapter(mContext, ((UserFolderInfo) info).contents));
@@ -91,4 +88,10 @@ public class UserFolder extends Folder implements DropTarget {
super.onOpen();
requestFocus();
}
+
+ @Override
+ public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
+ DragView dragView, Object dragInfo) {
+ return null;
+ }
}
diff --git a/src/com/android/launcher2/Utilities.java b/src/com/android/launcher2/Utilities.java
index 757e48e30..03a2a528a 100644
--- a/src/com/android/launcher2/Utilities.java
+++ b/src/com/android/launcher2/Utilities.java
@@ -16,9 +16,8 @@
package com.android.launcher2;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.PaintDrawable;
+import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
@@ -26,19 +25,19 @@ import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
-import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.TableMaskFilter;
import android.graphics.Typeface;
-import android.text.Layout.Alignment;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
import android.text.StaticLayout;
import android.text.TextPaint;
+import android.text.Layout.Alignment;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.content.res.Resources;
-import android.content.Context;
import com.android.launcher.R;
@@ -238,7 +237,7 @@ final class Utilities {
final DisplayMetrics metrics = resources.getDisplayMetrics();
final float density = metrics.density;
- sIconWidth = sIconHeight = (int) resources.getDimension(android.R.dimen.app_icon_size);
+ sIconWidth = sIconHeight = (int) resources.getDimension(R.dimen.app_icon_size);
sIconTextureWidth = sIconTextureHeight = sIconWidth + 2;
sBlurPaint.setMaskFilter(new BlurMaskFilter(5 * density, BlurMaskFilter.Blur.NORMAL));
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index c18220931..df3d2de52 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -18,147 +18,163 @@ package com.android.launcher2;
import java.util.ArrayList;
import java.util.HashSet;
-
+import java.util.List;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.AlertDialog;
import android.app.WallpaperManager;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
+import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.IBinder;
-import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.DragEvent;
import android.view.MotionEvent;
-import android.view.VelocityTracker;
import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.animation.Interpolator;
-import android.widget.Scroller;
+import android.view.animation.DecelerateInterpolator;
import android.widget.TextView;
+import android.widget.Toast;
import com.android.launcher.R;
+import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
/**
- * The workspace is a wide area with a wallpaper and a finite number of screens. Each
- * screen contains a number of icons, folders or widgets the user can interact with.
- * A workspace is meant to be used with a fixed width only.
+ * The workspace is a wide area with a wallpaper and a finite number of pages.
+ * Each page contains a number of icons, folders or widgets the user can
+ * interact with. A workspace is meant to be used with a fixed width only.
*/
-public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
+public class Workspace extends SmoothPagedView
+ implements DropTarget, DragSource, DragScroller, View.OnTouchListener {
@SuppressWarnings({"UnusedDeclaration"})
private static final String TAG = "Launcher.Workspace";
- private static final int INVALID_SCREEN = -1;
-
- /**
- * The velocity at which a fling gesture will cause us to snap to the next screen
- */
- private static final int SNAP_VELOCITY = 600;
+
+ // This is how much the workspace shrinks when we enter all apps or
+ // customization mode
+ private static final float SHRINK_FACTOR = 0.16f;
+
+ // Y rotation to apply to the workspace screens
+ private static final float WORKSPACE_ROTATION = 12.5f;
+
+ // These are extra scale factors to apply to the mini home screens
+ // so as to achieve the desired transform
+ private static final float EXTRA_SCALE_FACTOR_0 = 0.972f;
+ private static final float EXTRA_SCALE_FACTOR_1 = 1.0f;
+ private static final float EXTRA_SCALE_FACTOR_2 = 1.10f;
+
+ private static final int BACKGROUND_FADE_OUT_DELAY = 300;
+ private static final int BACKGROUND_FADE_OUT_DURATION = 300;
+ private static final int BACKGROUND_FADE_IN_DURATION = 100;
+
+ // These animators are used to fade the background
+ private ObjectAnimator mBackgroundFadeInAnimation;
+ private ObjectAnimator mBackgroundFadeOutAnimation;
+ private float mBackgroundAlpha = 0;
private final WallpaperManager mWallpaperManager;
-
- private int mDefaultScreen;
- private boolean mFirstLayout = true;
+ private int mDefaultPage;
- private int mCurrentScreen;
- private int mNextScreen = INVALID_SCREEN;
- private Scroller mScroller;
- private VelocityTracker mVelocityTracker;
+ private boolean mPageMoving = false;
/**
* CellInfo for the cell that is currently being dragged
*/
private CellLayout.CellInfo mDragInfo;
-
+
/**
* Target drop area calculated during last acceptDrop call.
*/
private int[] mTargetCell = null;
- private float mLastMotionX;
- private float mLastMotionY;
-
- private final static int TOUCH_STATE_REST = 0;
- private final static int TOUCH_STATE_SCROLLING = 1;
-
- private int mTouchState = TOUCH_STATE_REST;
-
- private OnLongClickListener mLongClickListener;
+ /**
+ * The CellLayout that is currently being dragged over
+ */
+ private CellLayout mDragTargetLayout = null;
private Launcher mLauncher;
private IconCache mIconCache;
private DragController mDragController;
-
- /**
- * Cache of vacant cells, used during drag events and invalidated as needed.
- */
- private CellLayout.CellInfo mVacantCache = null;
-
+
+ // These are temporary variables to prevent having to allocate a new object just to
+ // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
private int[] mTempCell = new int[2];
private int[] mTempEstimate = new int[2];
+ private float[] mTempOriginXY = new float[2];
+ private float[] mTempDragCoordinates = new float[2];
+ private float[] mTempTouchCoordinates = new float[2];
+ private float[] mTempCellLayoutCenterCoordinates = new float[2];
+ private float[] mTempDragBottomRightCoordinates = new float[2];
+ private Matrix mTempInverseMatrix = new Matrix();
- private boolean mAllowLongPress = true;
-
- private int mTouchSlop;
- private int mMaximumVelocity;
-
- private static final int INVALID_POINTER = -1;
+ private static final int DEFAULT_CELL_COUNT_X = 4;
+ private static final int DEFAULT_CELL_COUNT_Y = 4;
- private int mActivePointerId = INVALID_POINTER;
-
private Drawable mPreviousIndicator;
private Drawable mNextIndicator;
-
- private static final float NANOTIME_DIV = 1000000000.0f;
- private static final float SMOOTHING_SPEED = 0.75f;
- private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED));
- private float mSmoothingTime;
- private float mTouchX;
- private WorkspaceOvershootInterpolator mScrollInterpolator;
+ // State variable that indicates whether the pages are small (ie when you're
+ // in all apps or customize mode)
+ private boolean mIsSmall = false;
+ private boolean mIsInUnshrinkAnimation = false;
+ private AnimatorListener mUnshrinkAnimationListener;
+ private enum ShrinkPosition {
+ SHRINK_TO_TOP, SHRINK_TO_MIDDLE, SHRINK_TO_BOTTOM_HIDDEN, SHRINK_TO_BOTTOM_VISIBLE };
+ private ShrinkPosition mShrunkenState;
+ private boolean mWaitingToShrink = false;
+ private ShrinkPosition mWaitingToShrinkPosition;
+ private AnimatorSet mAnimator;
+
+ private boolean mInScrollArea = false;
+ private boolean mInDragMode = false;
+
+ private final HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper();
+ private Bitmap mDragOutline = null;
+ private final Rect mTempRect = new Rect();
+ private final int[] mTempXY = new int[2];
+
+ private ValueAnimator mDropAnim = null;
+ private TimeInterpolator mQuintEaseOutInterpolator = new DecelerateInterpolator(2.5f);
+ private View mDropView = null;
+ private int[] mDropViewPos = new int[] { -1, -1 };
+
+ // Paint used to draw external drop outline
+ private final Paint mExternalDragOutlinePaint = new Paint();
+
+ /** Used to trigger an animation as soon as the workspace stops scrolling. */
+ private Animator mAnimOnPageEndMoving = null;
- private static final float BASELINE_FLING_VELOCITY = 2500.f;
- private static final float FLING_VELOCITY_INFLUENCE = 0.4f;
-
- private static class WorkspaceOvershootInterpolator implements Interpolator {
- private static final float DEFAULT_TENSION = 1.3f;
- private float mTension;
-
- public WorkspaceOvershootInterpolator() {
- mTension = DEFAULT_TENSION;
- }
-
- public void setDistance(int distance) {
- mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
- }
-
- public void disableSettle() {
- mTension = 0.f;
- }
-
- public float getInterpolation(float t) {
- // _o(t) = t * t * ((tension + 1) * t + tension)
- // o(t) = _o(t - 1) + 1
- t -= 1.0f;
- return t * t * ((mTension + 1) * t + mTension) + 1.0f;
- }
- }
-
/**
* Used to inflate the Workspace from XML.
*
* @param context The application's context.
- * @param attrs The attribtues set containing the Workspace's customization values.
+ * @param attrs The attributes set containing the Workspace's customization values.
*/
public Workspace(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -168,37 +184,64 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
* Used to inflate the Workspace from XML.
*
* @param context The application's context.
- * @param attrs The attribtues set containing the Workspace's customization values.
+ * @param attrs The attributes set containing the Workspace's customization values.
* @param defStyle Unused.
*/
public Workspace(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ mContentIsRefreshable = false;
+
+ if (!LauncherApplication.isScreenXLarge()) {
+ mFadeInAdjacentScreens = false;
+ }
mWallpaperManager = WallpaperManager.getInstance(context);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
- mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.Workspace, defStyle, 0);
+ int cellCountX = a.getInt(R.styleable.Workspace_cellCountX, DEFAULT_CELL_COUNT_X);
+ int cellCountY = a.getInt(R.styleable.Workspace_cellCountY, DEFAULT_CELL_COUNT_Y);
+ mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
a.recycle();
+ LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY);
setHapticFeedbackEnabled(false);
+
initWorkspace();
}
/**
* Initializes various states for this workspace.
*/
- private void initWorkspace() {
+ protected void initWorkspace() {
Context context = getContext();
- mScrollInterpolator = new WorkspaceOvershootInterpolator();
- mScroller = new Scroller(context, mScrollInterpolator);
- mCurrentScreen = mDefaultScreen;
- Launcher.setScreen(mCurrentScreen);
+ mCurrentPage = mDefaultPage;
+ Launcher.setScreen(mCurrentPage);
LauncherApplication app = (LauncherApplication)context.getApplicationContext();
mIconCache = app.getIconCache();
+ mExternalDragOutlinePaint.setAntiAlias(true);
+ setWillNotDraw(false);
+
+ mUnshrinkAnimationListener = new LauncherAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mIsInUnshrinkAnimation = true;
+ }
+ @Override
+ public void onAnimationEndOrCancel(Animator animation) {
+ mIsInUnshrinkAnimation = false;
+ }
+ };
+ mSnapVelocity = 600;
+ }
- final ViewConfiguration configuration = ViewConfiguration.get(getContext());
- mTouchSlop = configuration.getScaledTouchSlop();
- mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ @Override
+ protected int getScrollMode() {
+ if (LauncherApplication.isScreenXLarge()) {
+ return SmoothPagedView.QUINTIC_MODE;
+ } else {
+ return SmoothPagedView.OVERSHOOT_MODE;
+ }
}
@Override
@@ -206,6 +249,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
if (!(child instanceof CellLayout)) {
throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
}
+ ((CellLayout) child).setOnInterceptTouchListener(this);
super.addView(child, index, params);
}
@@ -214,6 +258,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
if (!(child instanceof CellLayout)) {
throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
}
+ ((CellLayout) child).setOnInterceptTouchListener(this);
super.addView(child);
}
@@ -222,6 +267,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
if (!(child instanceof CellLayout)) {
throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
}
+ ((CellLayout) child).setOnInterceptTouchListener(this);
super.addView(child, index);
}
@@ -230,6 +276,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
if (!(child instanceof CellLayout)) {
throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
}
+ ((CellLayout) child).setOnInterceptTouchListener(this);
super.addView(child, width, height);
}
@@ -238,6 +285,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
if (!(child instanceof CellLayout)) {
throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
}
+ ((CellLayout) child).setOnInterceptTouchListener(this);
super.addView(child, params);
}
@@ -245,94 +293,52 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
* @return The open folder on the current screen, or null if there is none
*/
Folder getOpenFolder() {
- CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen);
- int count = currentScreen.getChildCount();
+ CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
+ int count = currentPage.getChildCount();
for (int i = 0; i < count; i++) {
- View child = currentScreen.getChildAt(i);
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
- if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
- return (Folder) child;
+ View child = currentPage.getChildAt(i);
+ if (child instanceof Folder) {
+ Folder folder = (Folder) child;
+ if (folder.getInfo().opened)
+ return folder;
}
}
return null;
}
ArrayList<Folder> getOpenFolders() {
- final int screens = getChildCount();
- ArrayList<Folder> folders = new ArrayList<Folder>(screens);
+ final int screenCount = getChildCount();
+ ArrayList<Folder> folders = new ArrayList<Folder>(screenCount);
- for (int screen = 0; screen < screens; screen++) {
- CellLayout currentScreen = (CellLayout) getChildAt(screen);
- int count = currentScreen.getChildCount();
+ for (int screen = 0; screen < screenCount; screen++) {
+ CellLayout currentPage = (CellLayout) getChildAt(screen);
+ int count = currentPage.getChildCount();
for (int i = 0; i < count; i++) {
- View child = currentScreen.getChildAt(i);
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
- if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
- folders.add((Folder) child);
+ View child = currentPage.getChildAt(i);
+ if (child instanceof Folder) {
+ Folder folder = (Folder) child;
+ if (folder.getInfo().opened)
+ folders.add(folder);
break;
}
}
}
-
return folders;
}
- boolean isDefaultScreenShowing() {
- return mCurrentScreen == mDefaultScreen;
- }
-
- /**
- * Returns the index of the currently displayed screen.
- *
- * @return The index of the currently displayed screen.
- */
- int getCurrentScreen() {
- return mCurrentScreen;
+ boolean isDefaultPageShowing() {
+ return mCurrentPage == mDefaultPage;
}
/**
* Sets the current screen.
*
- * @param currentScreen
- */
- void setCurrentScreen(int currentScreen) {
- if (!mScroller.isFinished()) mScroller.abortAnimation();
- clearVacantCache();
- mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
- mPreviousIndicator.setLevel(mCurrentScreen);
- mNextIndicator.setLevel(mCurrentScreen);
- scrollTo(mCurrentScreen * getWidth(), 0);
- updateWallpaperOffset();
- invalidate();
- }
-
- /**
- * Adds the specified child in the current screen. The position and dimension of
- * the child are defined by x, y, spanX and spanY.
- *
- * @param child The child to add in one of the workspace's screens.
- * @param x The X position of the child in the screen's grid.
- * @param y The Y position of the child in the screen's grid.
- * @param spanX The number of cells spanned horizontally by the child.
- * @param spanY The number of cells spanned vertically by the child.
- */
- void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) {
- addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false);
- }
-
- /**
- * Adds the specified child in the current screen. The position and dimension of
- * the child are defined by x, y, spanX and spanY.
- *
- * @param child The child to add in one of the workspace's screens.
- * @param x The X position of the child in the screen's grid.
- * @param y The Y position of the child in the screen's grid.
- * @param spanX The number of cells spanned horizontally by the child.
- * @param spanY The number of cells spanned vertically by the child.
- * @param insert When true, the child is inserted at the beginning of the children list.
+ * @param currentPage
*/
- void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) {
- addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert);
+ @Override
+ void setCurrentPage(int currentPage) {
+ super.setCurrentPage(currentPage);
+ updateWallpaperOffset(mScrollX);
}
/**
@@ -350,6 +356,10 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
addInScreen(child, screen, x, y, spanX, spanY, false);
}
+ void addInFullScreen(View child, int screen) {
+ addInScreen(child, screen, 0, 0, -1, -1);
+ }
+
/**
* Adds the specified child in the specified screen. The position and dimension of
* the child are defined by x, y, spanX and spanY.
@@ -369,8 +379,6 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
return;
}
- clearVacantCache();
-
final CellLayout group = (CellLayout) getChildAt(screen);
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
if (lp == null) {
@@ -381,124 +389,181 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
lp.cellHSpan = spanX;
lp.cellVSpan = spanY;
}
- group.addView(child, insert ? 0 : -1, lp);
+
+ // Get the canonical child id to uniquely represent this view in this screen
+ int childId = LauncherModel.getCellLayoutChildId(-1, screen, x, y, spanX, spanY);
+ boolean markCellsAsOccupied = !(child instanceof Folder);
+ if (!group.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
+ // TODO: This branch occurs when the workspace is adding views
+ // outside of the defined grid
+ // maybe we should be deleting these items from the LauncherModel?
+ Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
+ }
+
if (!(child instanceof Folder)) {
child.setHapticFeedbackEnabled(false);
child.setOnLongClickListener(mLongClickListener);
}
if (child instanceof DropTarget) {
- mDragController.addDropTarget((DropTarget)child);
+ mDragController.addDropTarget((DropTarget) child);
}
}
- CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
- CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
- if (group != null) {
- return group.findAllVacantCells(occupied, null);
+ public boolean onTouch(View v, MotionEvent event) {
+ // this is an intercepted event being forwarded from a cell layout
+ if (mIsSmall || mIsInUnshrinkAnimation) {
+ mLauncher.onWorkspaceClick((CellLayout) v);
+ return true;
+ } else if (!mPageMoving) {
+ if (v == getChildAt(mCurrentPage - 1)) {
+ snapToPage(mCurrentPage - 1);
+ return true;
+ } else if (v == getChildAt(mCurrentPage + 1)) {
+ snapToPage(mCurrentPage + 1);
+ return true;
+ }
}
- return null;
+ return false;
}
- private void clearVacantCache() {
- if (mVacantCache != null) {
- mVacantCache.clearVacantCells();
- mVacantCache = null;
+ @Override
+ public boolean dispatchUnhandledMove(View focused, int direction) {
+ if (mIsSmall || mIsInUnshrinkAnimation) {
+ // when the home screens are shrunken, shouldn't allow side-scrolling
+ return false;
}
+ return super.dispatchUnhandledMove(focused, direction);
}
- /**
- * Registers the specified listener on each screen contained in this workspace.
- *
- * @param l The listener used to respond to long clicks.
- */
@Override
- public void setOnLongClickListener(OnLongClickListener l) {
- mLongClickListener = l;
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- getChildAt(i).setOnLongClickListener(l);
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mIsSmall || mIsInUnshrinkAnimation) {
+ // when the home screens are shrunken, shouldn't allow side-scrolling
+ return false;
}
+ return super.onInterceptTouchEvent(ev);
}
- private void updateWallpaperOffset() {
- updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft));
+ @Override
+ protected void determineScrollingStart(MotionEvent ev) {
+ if (!mIsSmall && !mIsInUnshrinkAnimation) super.determineScrollingStart(ev);
}
- private void updateWallpaperOffset(int scrollRange) {
- IBinder token = getWindowToken();
- if (token != null) {
- mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 );
- mWallpaperManager.setWallpaperOffsets(getWindowToken(),
- Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0);
+ protected void onPageBeginMoving() {
+ if (mNextPage != INVALID_PAGE) {
+ // we're snapping to a particular screen
+ enableChildrenCache(mCurrentPage, mNextPage);
+ } else {
+ // this is when user is actively dragging a particular screen, they might
+ // swipe it either left or right (but we won't advance by more than one screen)
+ enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
}
+ showOutlines();
+ mPageMoving = true;
}
-
- @Override
- public void scrollTo(int x, int y) {
- super.scrollTo(x, y);
- mTouchX = x;
- mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+
+ protected void onPageEndMoving() {
+ clearChildrenCache();
+ // Hide the outlines, as long as we're not dragging
+ if (!mDragController.dragging()) {
+ hideOutlines();
+ }
+ // Check for an animation that's waiting to be started
+ if (mAnimOnPageEndMoving != null) {
+ mAnimOnPageEndMoving.start();
+ mAnimOnPageEndMoving = null;
+ }
+
+ mPageMoving = false;
}
-
+
@Override
- public void computeScroll() {
- if (mScroller.computeScrollOffset()) {
- mTouchX = mScrollX = mScroller.getCurrX();
- mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
- mScrollY = mScroller.getCurrY();
- updateWallpaperOffset();
- postInvalidate();
- } else if (mNextScreen != INVALID_SCREEN) {
- mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
- mPreviousIndicator.setLevel(mCurrentScreen);
- mNextIndicator.setLevel(mCurrentScreen);
- Launcher.setScreen(mCurrentScreen);
- mNextScreen = INVALID_SCREEN;
- clearChildrenCache();
- } else if (mTouchState == TOUCH_STATE_SCROLLING) {
- final float now = System.nanoTime() / NANOTIME_DIV;
- final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT);
- final float dx = mTouchX - mScrollX;
- mScrollX += dx * e;
- mSmoothingTime = now;
-
- // Keep generating points as long as we're more than 1px away from the target
- if (dx > 1.f || dx < -1.f) {
- updateWallpaperOffset();
- postInvalidate();
+ protected void notifyPageSwitchListener() {
+ super.notifyPageSwitchListener();
+
+ if (mPreviousIndicator != null) {
+ // if we know the next page, we show the indication for it right away; it looks
+ // weird if the indicators are lagging
+ int page = mNextPage;
+ if (page == INVALID_PAGE) {
+ page = mCurrentPage;
}
+ mPreviousIndicator.setLevel(page);
+ mNextIndicator.setLevel(page);
}
+ Launcher.setScreen(mCurrentPage);
+ };
+
+ private void updateWallpaperOffset() {
+ updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft));
}
- @Override
- protected void dispatchDraw(Canvas canvas) {
- boolean restore = false;
- int restoreCount = 0;
-
- // ViewGroup.dispatchDraw() supports many features we don't need:
- // clip to padding, layout animation, animation listener, disappearing
- // children, etc. The following implementation attempts to fast-track
- // the drawing dispatch by drawing only what we know needs to be drawn.
-
- boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;
- // If we are not scrolling or flinging, draw only the current screen
- if (fastDraw) {
- drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
- } else {
- final long drawingTime = getDrawingTime();
- final float scrollPos = (float) mScrollX / getWidth();
- final int leftScreen = (int) scrollPos;
- final int rightScreen = leftScreen + 1;
- if (leftScreen >= 0) {
- drawChild(canvas, getChildAt(leftScreen), drawingTime);
- }
- if (scrollPos != leftScreen && rightScreen < getChildCount()) {
- drawChild(canvas, getChildAt(rightScreen), drawingTime);
+ private void updateWallpaperOffset(int scrollRange) {
+ final boolean isStaticWallpaper = (mWallpaperManager != null) &&
+ (mWallpaperManager.getWallpaperInfo() == null);
+ if (LauncherApplication.isScreenXLarge() && !isStaticWallpaper) {
+ IBinder token = getWindowToken();
+ if (token != null) {
+ mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 );
+ mWallpaperManager.setWallpaperOffsets(getWindowToken(),
+ Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0);
}
}
+ }
+
+ public void showOutlines() {
+ if (!mIsSmall && !mIsInUnshrinkAnimation) {
+ if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel();
+ if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel();
+ mBackgroundFadeInAnimation = ObjectAnimator.ofFloat(this, "backgroundAlpha", 1.0f);
+ mBackgroundFadeInAnimation.setDuration(BACKGROUND_FADE_IN_DURATION);
+ mBackgroundFadeInAnimation.start();
+ }
+ }
+
+ public void hideOutlines() {
+ if (!mIsSmall && !mIsInUnshrinkAnimation) {
+ if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel();
+ if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel();
+ mBackgroundFadeOutAnimation = ObjectAnimator.ofFloat(this, "backgroundAlpha", 0.0f);
+ mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
+ mBackgroundFadeOutAnimation.setStartDelay(BACKGROUND_FADE_OUT_DELAY);
+ mBackgroundFadeOutAnimation.start();
+ }
+ }
+
+ public void setBackgroundAlpha(float alpha) {
+ mBackgroundAlpha = alpha;
+ for (int i = 0; i < getChildCount(); i++) {
+ CellLayout cl = (CellLayout) getChildAt(i);
+ cl.setBackgroundAlpha(alpha);
+ }
+ }
+
+ public float getBackgroundAlpha() {
+ return mBackgroundAlpha;
+ }
- if (restore) {
- canvas.restoreToCount(restoreCount);
+ @Override
+ protected void screenScrolled(int screenCenter) {
+ final int halfScreenSize = getMeasuredWidth() / 2;
+ for (int i = 0; i < getChildCount(); i++) {
+ CellLayout cl = (CellLayout) getChildAt(i);
+ if (cl != null) {
+ int totalDistance = cl.getMeasuredWidth() + mPageSpacing;
+ int delta = screenCenter - (getChildOffset(i) -
+ getRelativeChildOffset(i) + halfScreenSize);
+
+ float scrollProgress = delta/(totalDistance*1.0f);
+ scrollProgress = Math.min(scrollProgress, 1.0f);
+ scrollProgress = Math.max(scrollProgress, -1.0f);
+
+ float mult = mInDragMode ? 1.0f : Math.abs(scrollProgress);
+ cl.setBackgroundAlphaMultiplier(mult);
+
+ float rotation = WORKSPACE_ROTATION * scrollProgress;
+ cl.setRotationY(rotation);
+ }
}
}
@@ -509,59 +574,72 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
}
@Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
- final int width = MeasureSpec.getSize(widthMeasureSpec);
- final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- if (widthMode != MeasureSpec.EXACTLY) {
- throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
+ // if shrinkToBottom() is called on initialization, it has to be deferred
+ // until after the first call to onLayout so that it has the correct width
+ if (mWaitingToShrink) {
+ shrink(mWaitingToShrinkPosition, false);
+ mWaitingToShrink = false;
}
- final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- if (heightMode != MeasureSpec.EXACTLY) {
- throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
+ if (LauncherApplication.isInPlaceRotationEnabled()) {
+ // When the device is rotated, the scroll position of the current screen
+ // needs to be refreshed
+ setCurrentPage(getCurrentPage());
}
+ }
- // The children are given the same width and height as the workspace
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
- }
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ if (mIsSmall || mIsInUnshrinkAnimation) {
+ // Draw all the workspaces if we're small
+ final int pageCount = getChildCount();
+ final long drawingTime = getDrawingTime();
+ for (int i = 0; i < pageCount; i++) {
+ final View page = (View) getChildAt(i);
+ drawChild(canvas, page, drawingTime);
+ }
+ } else {
+ super.dispatchDraw(canvas);
- if (mFirstLayout) {
- setHorizontalScrollBarEnabled(false);
- scrollTo(mCurrentScreen * width, 0);
- setHorizontalScrollBarEnabled(true);
- updateWallpaperOffset(width * (getChildCount() - 1));
- mFirstLayout = false;
- }
- }
+ final int width = getWidth();
+ final int height = getHeight();
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- int childLeft = 0;
+ // In portrait orientation, draw the glowing edge when dragging to adjacent screens
+ if (mInScrollArea && (height > width)) {
+ final int pageHeight = getChildAt(0).getHeight();
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() != View.GONE) {
- final int childWidth = child.getMeasuredWidth();
- child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
- childLeft += childWidth;
+ // This determines the height of the glowing edge: 90% of the page height
+ final int padding = (int) ((height - pageHeight) * 0.5f + pageHeight * 0.1f);
+
+ final CellLayout leftPage = (CellLayout) getChildAt(mCurrentPage - 1);
+ final CellLayout rightPage = (CellLayout) getChildAt(mCurrentPage + 1);
+
+ if (leftPage != null && leftPage.getHover()) {
+ final Drawable d = getResources().getDrawable(R.drawable.page_hover_left);
+ d.setBounds(mScrollX, padding, mScrollX + d.getIntrinsicWidth(), height - padding);
+ d.draw(canvas);
+ } else if (rightPage != null && rightPage.getHover()) {
+ final Drawable d = getResources().getDrawable(R.drawable.page_hover_right);
+ d.setBounds(mScrollX + width - d.getIntrinsicWidth(), padding, mScrollX + width, height - padding);
+ d.draw(canvas);
+ }
}
- }
- }
- @Override
- public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
- int screen = indexOfChild(child);
- if (screen != mCurrentScreen || !mScroller.isFinished()) {
- snapToScreen(screen);
- return true;
+ if (mDropView != null) {
+ // We are animating an item that was just dropped on the home screen.
+ // Render its View in the current animation position.
+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ final int xPos = mDropViewPos[0] - mDropView.getScrollX();
+ final int yPos = mDropViewPos[1] - mDropView.getScrollY();
+ canvas.translate(xPos, yPos);
+ mDropView.draw(canvas);
+ canvas.restore();
+ }
}
- return false;
}
@Override
@@ -571,51 +649,20 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
if (openFolder != null) {
return openFolder.requestFocus(direction, previouslyFocusedRect);
} else {
- int focusableScreen;
- if (mNextScreen != INVALID_SCREEN) {
- focusableScreen = mNextScreen;
- } else {
- focusableScreen = mCurrentScreen;
- }
- getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
+ return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
}
}
return false;
}
@Override
- public boolean dispatchUnhandledMove(View focused, int direction) {
- if (direction == View.FOCUS_LEFT) {
- if (getCurrentScreen() > 0) {
- snapToScreen(getCurrentScreen() - 1);
- return true;
- }
- } else if (direction == View.FOCUS_RIGHT) {
- if (getCurrentScreen() < getChildCount() - 1) {
- snapToScreen(getCurrentScreen() + 1);
- return true;
- }
- }
- return super.dispatchUnhandledMove(focused, direction);
- }
-
- @Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
if (!mLauncher.isAllAppsVisible()) {
final Folder openFolder = getOpenFolder();
- if (openFolder == null) {
- getChildAt(mCurrentScreen).addFocusables(views, direction);
- if (direction == View.FOCUS_LEFT) {
- if (mCurrentScreen > 0) {
- getChildAt(mCurrentScreen - 1).addFocusables(views, direction);
- }
- } else if (direction == View.FOCUS_RIGHT){
- if (mCurrentScreen < getChildCount() - 1) {
- getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
- }
- }
- } else {
+ if (openFolder != null) {
openFolder.addFocusables(views, direction);
+ } else {
+ super.addFocusables(views, direction, focusableMode);
}
}
}
@@ -623,195 +670,28 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- if (mLauncher.isAllAppsVisible()) {
+ // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps
+ // ie when you click on a mini-screen, it zooms back to that screen)
+ if (!LauncherApplication.isScreenXLarge() && mLauncher.isAllAppsVisible()) {
return false;
}
}
return super.dispatchTouchEvent(ev);
}
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- final boolean allAppsVisible = mLauncher.isAllAppsVisible();
- if (allAppsVisible) {
- return false; // We don't want the events. Let them fall through to the all apps view.
- }
-
- /*
- * This method JUST determines whether we want to intercept the motion.
- * If we return true, onTouchEvent will be called and we do the actual
- * scrolling there.
- */
-
- /*
- * Shortcut the most recurring case: the user is in the dragging
- * state and he is moving his finger. We want to intercept this
- * motion.
- */
- final int action = ev.getAction();
- if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
- return true;
- }
-
- acquireVelocityTrackerAndAddMovement(ev);
-
- switch (action & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_MOVE: {
- /*
- * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
- * whether the user has moved far enough from his original down touch.
- */
-
- /*
- * Locally do absolute value. mLastMotionX is set to the y value
- * of the down event.
- */
- final int pointerIndex = ev.findPointerIndex(mActivePointerId);
- final float x = ev.getX(pointerIndex);
- final float y = ev.getY(pointerIndex);
- final int xDiff = (int) Math.abs(x - mLastMotionX);
- final int yDiff = (int) Math.abs(y - mLastMotionY);
-
- final int touchSlop = mTouchSlop;
- boolean xMoved = xDiff > touchSlop;
- boolean yMoved = yDiff > touchSlop;
-
- if (xMoved || yMoved) {
-
- if (xMoved) {
- // Scroll if the user moved far enough along the X axis
- mTouchState = TOUCH_STATE_SCROLLING;
- mLastMotionX = x;
- mTouchX = mScrollX;
- mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
- enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);
- }
- // Either way, cancel any pending longpress
- if (mAllowLongPress) {
- mAllowLongPress = false;
- // Try canceling the long press. It could also have been scheduled
- // by a distant descendant, so use the mAllowLongPress flag to block
- // everything
- final View currentScreen = getChildAt(mCurrentScreen);
- currentScreen.cancelLongPress();
- }
- }
- break;
- }
-
- case MotionEvent.ACTION_DOWN: {
- final float x = ev.getX();
- final float y = ev.getY();
- // Remember location of down touch
- mLastMotionX = x;
- mLastMotionY = y;
- mActivePointerId = ev.getPointerId(0);
- mAllowLongPress = true;
-
- /*
- * If being flinged and user touches the screen, initiate drag;
- * otherwise don't. mScroller.isFinished should be false when
- * being flinged.
- */
- mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
- break;
- }
-
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
-
- if (mTouchState != TOUCH_STATE_SCROLLING) {
- final CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
- if (!currentScreen.lastDownOnOccupiedCell()) {
- getLocationOnScreen(mTempCell);
- // Send a tap to the wallpaper if the last down was on empty space
- final int pointerIndex = ev.findPointerIndex(mActivePointerId);
- mWallpaperManager.sendWallpaperCommand(getWindowToken(),
- "android.wallpaper.tap",
- mTempCell[0] + (int) ev.getX(pointerIndex),
- mTempCell[1] + (int) ev.getY(pointerIndex), 0, null);
- }
- }
-
- // Release the drag
- clearChildrenCache();
- mTouchState = TOUCH_STATE_REST;
- mActivePointerId = INVALID_POINTER;
- mAllowLongPress = false;
- releaseVelocityTracker();
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- onSecondaryPointerUp(ev);
- break;
- }
-
- /*
- * The only time we want to intercept motion events is if we are in the
- * drag mode.
- */
- return mTouchState != TOUCH_STATE_REST;
- }
-
- private void onSecondaryPointerUp(MotionEvent ev) {
- final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
- MotionEvent.ACTION_POINTER_INDEX_SHIFT;
- final int pointerId = ev.getPointerId(pointerIndex);
- if (pointerId == mActivePointerId) {
- // This was our active pointer going up. Choose a new
- // active pointer and adjust accordingly.
- // TODO: Make this decision more intelligent.
- final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
- mLastMotionX = ev.getX(newPointerIndex);
- mLastMotionY = ev.getY(newPointerIndex);
- mActivePointerId = ev.getPointerId(newPointerIndex);
- if (mVelocityTracker != null) {
- mVelocityTracker.clear();
- }
- }
- }
-
- /**
- * If one of our descendant views decides that it could be focused now, only
- * pass that along if it's on the current screen.
- *
- * This happens when live folders requery, and if they're off screen, they
- * end up calling requestFocus, which pulls it on screen.
- */
- @Override
- public void focusableViewAvailable(View focused) {
- View current = getChildAt(mCurrentScreen);
- View v = focused;
- while (true) {
- if (v == current) {
- super.focusableViewAvailable(focused);
- return;
- }
- if (v == this) {
- return;
- }
- ViewParent parent = v.getParent();
- if (parent instanceof View) {
- v = (View)v.getParent();
- } else {
- return;
- }
+ void enableChildrenCache(int fromPage, int toPage) {
+ if (fromPage > toPage) {
+ final int temp = fromPage;
+ fromPage = toPage;
+ toPage = temp;
}
- }
- void enableChildrenCache(int fromScreen, int toScreen) {
- if (fromScreen > toScreen) {
- final int temp = fromScreen;
- fromScreen = toScreen;
- toScreen = temp;
- }
-
- final int count = getChildCount();
+ final int screenCount = getChildCount();
- fromScreen = Math.max(fromScreen, 0);
- toScreen = Math.min(toScreen, count - 1);
+ fromPage = Math.max(fromPage, 0);
+ toPage = Math.min(toPage, screenCount - 1);
- for (int i = fromScreen; i <= toScreen; i++) {
+ for (int i = fromPage; i <= toPage; i++) {
final CellLayout layout = (CellLayout) getChildAt(i);
layout.setChildrenDrawnWithCacheEnabled(true);
layout.setChildrenDrawingCacheEnabled(true);
@@ -819,8 +699,8 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
}
void clearChildrenCache() {
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
+ final int screenCount = getChildCount();
+ for (int i = 0; i < screenCount; i++) {
final CellLayout layout = (CellLayout) getChildAt(i);
layout.setChildrenDrawnWithCacheEnabled(false);
}
@@ -828,384 +708,1174 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
@Override
public boolean onTouchEvent(MotionEvent ev) {
-
if (mLauncher.isAllAppsVisible()) {
// Cancel any scrolling that is in progress.
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
- snapToScreen(mCurrentScreen);
+ setCurrentPage(mCurrentPage);
return false; // We don't want the events. Let them fall through to the all apps view.
}
- acquireVelocityTrackerAndAddMovement(ev);
+ return super.onTouchEvent(ev);
+ }
- final int action = ev.getAction();
+ public boolean isSmall() {
+ return mIsSmall;
+ }
- switch (action & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN:
- /*
- * If being flinged and user touches, stop the fling. isFinished
- * will be false if being flinged.
- */
- if (!mScroller.isFinished()) {
- mScroller.abortAnimation();
- }
+ void shrinkToTop(boolean animated) {
+ shrink(ShrinkPosition.SHRINK_TO_TOP, animated);
+ }
+
+ void shrinkToMiddle() {
+ shrink(ShrinkPosition.SHRINK_TO_MIDDLE, true);
+ }
+
+ void shrinkToBottom() {
+ shrinkToBottom(true);
+ }
+
+ void shrinkToBottom(boolean animated) {
+ shrink(ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN, animated);
+ }
+
+ private float getYScaleForScreen(int screen) {
+ int x = Math.abs(screen - 2);
+
+ // TODO: This should be generalized for use with arbitrary rotation angles.
+ switch(x) {
+ case 0: return EXTRA_SCALE_FACTOR_0;
+ case 1: return EXTRA_SCALE_FACTOR_1;
+ case 2: return EXTRA_SCALE_FACTOR_2;
+ }
+ return 1.0f;
+ }
- // Remember where the motion event started
- mLastMotionX = ev.getX();
- mActivePointerId = ev.getPointerId(0);
- if (mTouchState == TOUCH_STATE_SCROLLING) {
- enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);
+ // we use this to shrink the workspace for the all apps view and the customize view
+ private void shrink(ShrinkPosition shrinkPosition, boolean animated) {
+ if (mFirstLayout) {
+ // (mFirstLayout == "first layout has not happened yet")
+ // if we get a call to shrink() as part of our initialization (for example, if
+ // Launcher is started in All Apps mode) then we need to wait for a layout call
+ // to get our width so we can layout the mini-screen views correctly
+ mWaitingToShrink = true;
+ mWaitingToShrinkPosition = shrinkPosition;
+ return;
+ }
+ mIsSmall = true;
+ mShrunkenState = shrinkPosition;
+
+ // Stop any scrolling, move to the current page right away
+ setCurrentPage((mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage);
+ updateWhichPagesAcceptDrops(mShrunkenState);
+
+ // we intercept and reject all touch events when we're small, so be sure to reset the state
+ mTouchState = TOUCH_STATE_REST;
+ mActivePointerId = INVALID_POINTER;
+
+ CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
+ currentPage.setBackgroundAlphaMultiplier(1.0f);
+
+ final Resources res = getResources();
+ final int screenWidth = getWidth();
+ final int screenHeight = getHeight();
+
+ // Making the assumption that all pages have the same width as the 0th
+ final int pageWidth = getChildAt(0).getMeasuredWidth();
+ final int pageHeight = getChildAt(0).getMeasuredHeight();
+
+ final int scaledPageWidth = (int) (SHRINK_FACTOR * pageWidth);
+ final int scaledPageHeight = (int) (SHRINK_FACTOR * pageHeight);
+ final float extraScaledSpacing = res.getDimension(R.dimen.smallScreenExtraSpacing);
+
+ final int screenCount = getChildCount();
+ float totalWidth = screenCount * scaledPageWidth + (screenCount - 1) * extraScaledSpacing;
+
+ boolean isPortrait = getMeasuredHeight() > getMeasuredWidth();
+ float newY = (isPortrait ?
+ getResources().getDimension(R.dimen.allAppsSmallScreenVerticalMarginPortrait) :
+ getResources().getDimension(R.dimen.allAppsSmallScreenVerticalMarginLandscape));
+ float finalAlpha = 1.0f;
+ float extraShrinkFactor = 1.0f;
+ if (shrinkPosition == ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE) {
+ newY = screenHeight - newY - scaledPageHeight;
+ } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN) {
+
+ // We shrink and disappear to nothing in the case of all apps
+ // (which is when we shrink to the bottom)
+ newY = screenHeight - newY - scaledPageHeight;
+ finalAlpha = 0.25f;
+ } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_MIDDLE) {
+ newY = screenHeight / 2 - scaledPageHeight / 2;
+ finalAlpha = 1.0f;
+ } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_TOP) {
+ newY = (isPortrait ?
+ getResources().getDimension(R.dimen.customizeSmallScreenVerticalMarginPortrait) :
+ getResources().getDimension(R.dimen.customizeSmallScreenVerticalMarginLandscape));
+ }
+
+ // We animate all the screens to the centered position in workspace
+ // At the same time, the screens become greyed/dimmed
+
+ // newX is initialized to the left-most position of the centered screens
+ float newX = mScroller.getFinalX() + screenWidth / 2 - totalWidth / 2;
+
+ // We are going to scale about the center of the view, so we need to adjust the positions
+ // of the views accordingly
+ newX -= (pageWidth - scaledPageWidth) / 2.0f;
+ newY -= (pageHeight - scaledPageHeight) / 2.0f;
+
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ mAnimator = new AnimatorSet();
+ for (int i = 0; i < screenCount; i++) {
+ CellLayout cl = (CellLayout) getChildAt(i);
+
+ float rotation = (-i + 2) * WORKSPACE_ROTATION;
+ float rotationScaleX = (float) (1.0f / Math.cos(Math.PI * rotation / 180.0f));
+ float rotationScaleY = getYScaleForScreen(i);
+
+ if (animated) {
+ final int duration = res.getInteger(R.integer.config_workspaceShrinkTime);
+ ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(cl,
+ PropertyValuesHolder.ofFloat("x", newX),
+ PropertyValuesHolder.ofFloat("y", newY),
+ PropertyValuesHolder.ofFloat("scaleX",
+ SHRINK_FACTOR * rotationScaleX * extraShrinkFactor),
+ PropertyValuesHolder.ofFloat("scaleY",
+ SHRINK_FACTOR * rotationScaleY * extraShrinkFactor),
+ PropertyValuesHolder.ofFloat("backgroundAlpha", finalAlpha),
+ PropertyValuesHolder.ofFloat("alpha", finalAlpha),
+ PropertyValuesHolder.ofFloat("rotationY", rotation));
+ anim.setDuration(duration);
+ mAnimator.playTogether(anim);
+ } else {
+ cl.setX((int)newX);
+ cl.setY((int)newY);
+ cl.setScaleX(SHRINK_FACTOR * rotationScaleX * extraShrinkFactor);
+ cl.setScaleY(SHRINK_FACTOR * rotationScaleY * extraShrinkFactor);
+ cl.setBackgroundAlpha(finalAlpha);
+ cl.setAlpha(finalAlpha);
+ cl.setRotationY(rotation);
}
- break;
- case MotionEvent.ACTION_MOVE:
- if (mTouchState == TOUCH_STATE_SCROLLING) {
- // Scroll to follow the motion event
- final int pointerIndex = ev.findPointerIndex(mActivePointerId);
- final float x = ev.getX(pointerIndex);
- final float deltaX = mLastMotionX - x;
- mLastMotionX = x;
-
- if (deltaX < 0) {
- if (mTouchX > 0) {
- mTouchX += Math.max(-mTouchX, deltaX);
- mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
- invalidate();
+ // increment newX for the next screen
+ newX += scaledPageWidth + extraScaledSpacing;
+ }
+ if (animated) {
+ mAnimator.start();
+ }
+ setChildrenDrawnWithCacheEnabled(true);
+ }
+
+
+ private void updateWhichPagesAcceptDrops(ShrinkPosition state) {
+ updateWhichPagesAcceptDropsHelper(state, false, 1, 1);
+ }
+
+
+ private void updateWhichPagesAcceptDropsDuringDrag(ShrinkPosition state, int spanX, int spanY) {
+ updateWhichPagesAcceptDropsHelper(state, true, spanX, spanY);
+ }
+
+ private void updateWhichPagesAcceptDropsHelper(
+ ShrinkPosition state, boolean isDragHappening, int spanX, int spanY) {
+ final int screenCount = getChildCount();
+ for (int i = 0; i < screenCount; i++) {
+ CellLayout cl = (CellLayout) getChildAt(i);
+
+ switch (state) {
+ case SHRINK_TO_TOP:
+ if (!isDragHappening) {
+ boolean showDropHighlight = i == mCurrentPage;
+ cl.setAcceptsDrops(showDropHighlight);
+ break;
}
- } else if (deltaX > 0) {
- final float availableToScroll = getChildAt(getChildCount() - 1).getRight() -
- mTouchX - getWidth();
- if (availableToScroll > 0) {
- mTouchX += Math.min(availableToScroll, deltaX);
- mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
- invalidate();
+ // otherwise, fall through below and mark non-full screens as accepting drops
+ case SHRINK_TO_BOTTOM_HIDDEN:
+ case SHRINK_TO_BOTTOM_VISIBLE:
+ if (!isDragHappening) {
+ // even if a drag isn't happening, we don't want to show a screen as
+ // accepting drops if it doesn't have at least one free cell
+ spanX = 1;
+ spanY = 1;
}
- } else {
- awakenScrollBars();
- }
- }
- break;
- case MotionEvent.ACTION_UP:
- if (mTouchState == TOUCH_STATE_SCROLLING) {
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- final int velocityX = (int) velocityTracker.getXVelocity(mActivePointerId);
-
- final int screenWidth = getWidth();
- final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
- final float scrolledPos = (float) mScrollX / screenWidth;
-
- if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
- // Fling hard enough to move left.
- // Don't fling across more than one screen at a time.
- final int bound = scrolledPos < whichScreen ?
- mCurrentScreen - 1 : mCurrentScreen;
- snapToScreen(Math.min(whichScreen, bound), velocityX, true);
- } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
- // Fling hard enough to move right
- // Don't fling across more than one screen at a time.
- final int bound = scrolledPos > whichScreen ?
- mCurrentScreen + 1 : mCurrentScreen;
- snapToScreen(Math.max(whichScreen, bound), velocityX, true);
- } else {
- snapToScreen(whichScreen, 0, true);
- }
- }
- mTouchState = TOUCH_STATE_REST;
- mActivePointerId = INVALID_POINTER;
- releaseVelocityTracker();
- break;
- case MotionEvent.ACTION_CANCEL:
- if (mTouchState == TOUCH_STATE_SCROLLING) {
- final int screenWidth = getWidth();
- final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
- snapToScreen(whichScreen, 0, true);
+ // the page accepts drops if we can find at least one empty spot
+ cl.setAcceptsDrops(cl.findCellForSpan(null, spanX, spanY));
+ break;
+ default:
+ throw new RuntimeException(
+ "updateWhichPagesAcceptDropsHelper passed an unhandled ShrinkPosition");
}
- mTouchState = TOUCH_STATE_REST;
- mActivePointerId = INVALID_POINTER;
- releaseVelocityTracker();
- break;
- case MotionEvent.ACTION_POINTER_UP:
- onSecondaryPointerUp(ev);
- break;
}
+ }
- return true;
+ /*
+ *
+ * We call these methods (onDragStartedWithItemSpans/onDragStartedWithItemMinSize) whenever we
+ * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
+ *
+ * These methods mark the appropriate pages as accepting drops (which alters their visual
+ * appearance) and, if the pages are hidden, makes them visible.
+ *
+ */
+ public void onDragStartedWithItemSpans(int spanX, int spanY) {
+ updateWhichPagesAcceptDropsDuringDrag(mShrunkenState, spanX, spanY);
+ if (mShrunkenState == ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN) {
+ shrink(ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE, true);
+ }
}
-
- private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
+
+ public void onDragStartedWithItemMinSize(int minWidth, int minHeight) {
+ int[] spanXY = CellLayout.rectToCell(getResources(), minWidth, minHeight, null);
+ onDragStartedWithItemSpans(spanXY[0], spanXY[1]);
+ }
+
+ // we call this method whenever a drag and drop in Launcher finishes, even if Workspace was
+ // never dragged over
+ public void onDragStopped() {
+ updateWhichPagesAcceptDrops(mShrunkenState);
+ if (mShrunkenState == ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE) {
+ shrink(ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN, true);
}
- mVelocityTracker.addMovement(ev);
}
- private void releaseVelocityTracker() {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
+ // We call this when we trigger an unshrink by clicking on the CellLayout cl
+ public void unshrink(CellLayout clThatWasClicked) {
+ int newCurrentPage = mCurrentPage;
+ final int screenCount = getChildCount();
+ for (int i = 0; i < screenCount; i++) {
+ if (getChildAt(i) == clThatWasClicked) {
+ newCurrentPage = i;
+ }
}
+ unshrink(newCurrentPage);
}
- void snapToScreen(int whichScreen) {
- snapToScreen(whichScreen, 0, false);
+ @Override
+ protected boolean handlePagingClicks() {
+ return true;
}
- private void snapToScreen(int whichScreen, int velocity, boolean settle) {
- //if (!mScroller.isFinished()) return;
+ private void unshrink(int newCurrentPage) {
+ if (mIsSmall) {
+ int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage);
+ int delta = newX - mScrollX;
- whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
-
- clearVacantCache();
- enableChildrenCache(mCurrentScreen, whichScreen);
+ final int screenCount = getChildCount();
+ for (int i = 0; i < screenCount; i++) {
+ CellLayout cl = (CellLayout) getChildAt(i);
+ cl.setX(cl.getX() + delta);
+ }
+ setCurrentPage(newCurrentPage);
+ unshrink();
+ }
+ }
- mNextScreen = whichScreen;
+ void unshrink() {
+ unshrink(true);
+ }
- mPreviousIndicator.setLevel(mNextScreen);
- mNextIndicator.setLevel(mNextScreen);
+ void unshrink(boolean animated) {
+ if (mIsSmall) {
+ mIsSmall = false;
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ mAnimator = new AnimatorSet();
+ final int screenCount = getChildCount();
+
+ final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime);
+ for (int i = 0; i < screenCount; i++) {
+ final CellLayout cl = (CellLayout)getChildAt(i);
+ float finalAlphaValue = (i == mCurrentPage) ? 1.0f : 0.0f;
+ float rotation = 0.0f;
+
+ if (i < mCurrentPage) {
+ rotation = WORKSPACE_ROTATION;
+ } else if (i > mCurrentPage) {
+ rotation = -WORKSPACE_ROTATION;
+ }
- View focusedChild = getFocusedChild();
- if (focusedChild != null && whichScreen != mCurrentScreen &&
- focusedChild == getChildAt(mCurrentScreen)) {
- focusedChild.clearFocus();
+ if (animated) {
+ mAnimator.playTogether(
+ ObjectAnimator.ofFloat(cl, "translationX", 0.0f).setDuration(duration),
+ ObjectAnimator.ofFloat(cl, "translationY", 0.0f).setDuration(duration),
+ ObjectAnimator.ofFloat(cl, "scaleX", 1.0f).setDuration(duration),
+ ObjectAnimator.ofFloat(cl, "scaleY", 1.0f).setDuration(duration),
+ ObjectAnimator.ofFloat(cl, "backgroundAlpha", 0.0f).setDuration(duration),
+ ObjectAnimator.ofFloat(cl, "alpha", finalAlphaValue).setDuration(duration),
+ ObjectAnimator.ofFloat(cl, "rotationY", rotation).setDuration(duration));
+ } else {
+ cl.setTranslationX(0.0f);
+ cl.setTranslationY(0.0f);
+ cl.setScaleX(1.0f);
+ cl.setScaleY(1.0f);
+ cl.setBackgroundAlpha(0.0f);
+ cl.setAlpha(finalAlphaValue);
+ cl.setRotationY(rotation);
+ }
+ }
+ if (animated) {
+ // If we call this when we're not animated, onAnimationEnd is never called on
+ // the listener; make sure we only use the listener when we're actually animating
+ mAnimator.addListener(mUnshrinkAnimationListener);
+ mAnimator.start();
+ }
}
-
- final int screenDelta = Math.max(1, Math.abs(whichScreen - mCurrentScreen));
- final int newX = whichScreen * getWidth();
- final int delta = newX - mScrollX;
- int duration = (screenDelta + 1) * 100;
+ }
- if (!mScroller.isFinished()) {
- mScroller.abortAnimation();
- }
-
- if (settle) {
- mScrollInterpolator.setDistance(screenDelta);
- } else {
- mScrollInterpolator.disableSettle();
- }
-
- velocity = Math.abs(velocity);
- if (velocity > 0) {
- duration += (duration / (velocity / BASELINE_FLING_VELOCITY))
- * FLING_VELOCITY_INFLUENCE;
- } else {
- duration += 100;
+ /**
+ * Draw the View v into the given Canvas.
+ *
+ * @param v the view to draw
+ * @param destCanvas the canvas to draw on
+ * @param padding the horizontal and vertical padding to use when drawing
+ */
+ private void drawDragView(View v, Canvas destCanvas, int padding) {
+ final Rect clipRect = mTempRect;
+ v.getDrawingRect(clipRect);
+
+ // For a TextView, adjust the clip rect so that we don't include the text label
+ if (v instanceof TextView) {
+ final int iconHeight = ((TextView)v).getCompoundPaddingTop() - v.getPaddingTop();
+ clipRect.bottom = clipRect.top + iconHeight;
}
- awakenScrollBars(duration);
- mScroller.startScroll(mScrollX, 0, delta, 0, duration);
- invalidate();
+ // Draw the View into the bitmap.
+ // The translate of scrollX and scrollY is necessary when drawing TextViews, because
+ // they set scrollX and scrollY to large values to achieve centered text
+
+ destCanvas.save();
+ destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
+ destCanvas.clipRect(clipRect, Op.REPLACE);
+ v.draw(destCanvas);
+ destCanvas.restore();
+ }
+
+ /**
+ * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
+ * Responsibility for the bitmap is transferred to the caller.
+ */
+ private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
+ final int outlineColor = getResources().getColor(R.color.drag_outline_color);
+ final Bitmap b = Bitmap.createBitmap(
+ v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
+
+ canvas.setBitmap(b);
+ drawDragView(v, canvas, padding);
+ mOutlineHelper.applyExpensiveOuterOutline(b, canvas, outlineColor, true);
+
+ return b;
+ }
+
+ /**
+ * Creates a drag outline to represent a drop (that we don't have the actual information for
+ * yet). May be changed in the future to alter the drop outline slightly depending on the
+ * clip description mime data.
+ */
+ private Bitmap createExternalDragOutline(Canvas canvas, int padding) {
+ Resources r = getResources();
+ final int outlineColor = r.getColor(R.color.drag_outline_color);
+ final int iconWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
+ final int iconHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
+ final int rectRadius = r.getDimensionPixelSize(R.dimen.external_drop_icon_rect_radius);
+ final int inset = (int) (Math.min(iconWidth, iconHeight) * 0.2f);
+ final Bitmap b = Bitmap.createBitmap(
+ iconWidth + padding, iconHeight + padding, Bitmap.Config.ARGB_8888);
+
+ canvas.setBitmap(b);
+ canvas.drawRoundRect(new RectF(inset, inset, iconWidth - inset, iconHeight - inset),
+ rectRadius, rectRadius, mExternalDragOutlinePaint);
+ mOutlineHelper.applyExpensiveOuterOutline(b, canvas, outlineColor, true);
+
+ return b;
+ }
+
+ /**
+ * Returns a new bitmap to show when the given View is being dragged around.
+ * Responsibility for the bitmap is transferred to the caller.
+ */
+ private Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
+ final int outlineColor = getResources().getColor(R.color.drag_outline_color);
+ final Bitmap b = Bitmap.createBitmap(
+ mDragOutline.getWidth(), mDragOutline.getHeight(), Bitmap.Config.ARGB_8888);
+
+ canvas.setBitmap(b);
+ canvas.drawBitmap(mDragOutline, 0, 0, null);
+ drawDragView(v, canvas, padding);
+ mOutlineHelper.applyOuterBlur(b, canvas, outlineColor);
+
+ return b;
}
void startDrag(CellLayout.CellInfo cellInfo) {
View child = cellInfo.cell;
-
+
// Make sure the drag was started by a long press as opposed to a long click.
if (!child.isInTouchMode()) {
return;
}
-
+
mDragInfo = cellInfo;
- mDragInfo.screen = mCurrentScreen;
-
- CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));
+ mDragInfo.screen = mCurrentPage;
+
+ CellLayout current = getCurrentDropLayout();
current.onDragChild(child);
- mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
- invalidate();
+ child.setVisibility(View.GONE);
+
+ child.clearFocus();
+ child.setPressed(false);
+
+ final Canvas canvas = new Canvas();
+
+ // We need to add extra padding to the bitmap to make room for the glow effect
+ final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS;
+
+ // The outline is used to visualize where the item will land if dropped
+ mDragOutline = createDragOutline(child, canvas, bitmapPadding);
+
+ // The drag bitmap follows the touch point around on the screen
+ final Bitmap b = createDragBitmap(child, canvas, bitmapPadding);
+
+ final int bmpWidth = b.getWidth();
+ final int bmpHeight = b.getHeight();
+ child.getLocationOnScreen(mTempXY);
+ final int screenX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;
+ final int screenY = (int) mTempXY[1] + (child.getHeight() - bmpHeight) / 2;
+ mDragController.startDrag(b, screenX, screenY, 0, 0, bmpWidth, bmpHeight, this,
+ child.getTag(), DragController.DRAG_ACTION_MOVE, null);
+ b.recycle();
}
- @Override
- protected Parcelable onSaveInstanceState() {
- final SavedState state = new SavedState(super.onSaveInstanceState());
- state.currentScreen = mCurrentScreen;
- return state;
+ void addApplicationShortcut(ShortcutInfo info, int screen, int cellX, int cellY,
+ boolean insertAtFirst, int intersectX, int intersectY) {
+ final CellLayout cellLayout = (CellLayout) getChildAt(screen);
+ View view = mLauncher.createShortcut(R.layout.application, cellLayout, (ShortcutInfo) info);
+
+ final int[] cellXY = new int[2];
+ cellLayout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
+ addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
+ LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
+ cellXY[0], cellXY[1]);
}
- @Override
- protected void onRestoreInstanceState(Parcelable state) {
- SavedState savedState = (SavedState) state;
- super.onRestoreInstanceState(savedState.getSuperState());
- if (savedState.currentScreen != -1) {
- mCurrentScreen = savedState.currentScreen;
- Launcher.setScreen(mCurrentScreen);
- }
+ private void setPositionForDropAnimation(
+ View dragView, int dragViewX, int dragViewY, View parent, View child) {
+ final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+
+ // Based on the position of the drag view, find the top left of the original view
+ int viewX = dragViewX + (dragView.getWidth() - child.getWidth()) / 2;
+ int viewY = dragViewY + (dragView.getHeight() - child.getHeight()) / 2;
+ viewX -= getResources().getInteger(R.integer.config_dragViewOffsetX);
+ viewY -= getResources().getInteger(R.integer.config_dragViewOffsetY);
+
+ // Set its old pos (in the new parent's coordinates); it will be animated
+ // in animateViewIntoPosition after the next layout pass
+ lp.oldX = viewX - (parent.getLeft() - mScrollX);
+ lp.oldY = viewY - (parent.getTop() - mScrollY);
}
- void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo) {
- addApplicationShortcut(info, cellInfo, false);
+ public void animateViewIntoPosition(final View view) {
+ final CellLayout parent = (CellLayout) view.getParent();
+ final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
+
+ // Convert the animation params to be relative to the Workspace, not the CellLayout
+ final int fromX = lp.oldX + parent.getLeft();
+ final int fromY = lp.oldY + parent.getTop();
+
+ final int dx = lp.x - lp.oldX;
+ final int dy = lp.y - lp.oldY;
+
+ // Calculate the duration of the animation based on the object's distance
+ final float dist = (float) Math.sqrt(dx*dx + dy*dy);
+ final Resources res = getResources();
+ final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
+ int duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
+ if (dist < maxDist) {
+ duration *= mQuintEaseOutInterpolator.getInterpolation(dist / maxDist);
+ }
+
+ if (mDropAnim != null) {
+ // This should really be end(), but that will not be called synchronously,
+ // so instead we use LauncherAnimatorListenerAdapter.onAnimationEndOrCancel()
+ // and call cancel() here.
+ mDropAnim.cancel();
+ }
+ mDropAnim = new ValueAnimator();
+ mDropAnim.setInterpolator(mQuintEaseOutInterpolator);
+
+ // The view is invisible during the animation; we render it manually.
+ mDropAnim.addListener(new LauncherAnimatorListenerAdapter() {
+ public void onAnimationStart(Animator animation) {
+ // Set this here so that we don't render it until the animation begins
+ mDropView = view;
+ }
+
+ public void onAnimationEndOrCancel(Animator animation) {
+ if (mDropView != null) {
+ mDropView.setVisibility(View.VISIBLE);
+ mDropView = null;
+ }
+ }
+ });
+
+ mDropAnim.setDuration(duration);
+ mDropAnim.setFloatValues(0.0f, 1.0f);
+ mDropAnim.removeAllUpdateListeners();
+ mDropAnim.addUpdateListener(new AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final float percent = (Float) animation.getAnimatedValue();
+ // Invalidate the old position
+ invalidate(mDropViewPos[0], mDropViewPos[1],
+ mDropViewPos[0] + view.getWidth(), mDropViewPos[1] + view.getHeight());
+
+ mDropViewPos[0] = fromX + (int) (percent * dx + 0.5f);
+ mDropViewPos[1] = fromY + (int) (percent * dy + 0.5f);
+ invalidate(mDropViewPos[0], mDropViewPos[1],
+ mDropViewPos[0] + view.getWidth(), mDropViewPos[1] + view.getHeight());
+ }
+ });
+
+
+ view.setVisibility(View.INVISIBLE);
+
+ if (!mScroller.isFinished()) {
+ mAnimOnPageEndMoving = mDropAnim;
+ } else {
+ mDropAnim.start();
+ }
}
- void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo,
- boolean insertAtFirst) {
- final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen);
- final int[] result = new int[2];
+ /**
+ * {@inheritDoc}
+ */
+ public boolean acceptDrop(DragSource source, int x, int y,
+ int xOffset, int yOffset, DragView dragView, Object dragInfo) {
+
+ // If it's an external drop (e.g. from All Apps), check if it should be accepted
+ if (source != this) {
+ // Don't accept the drop if we're not over a screen at time of drop
+ if (mDragTargetLayout == null) {
+ return false;
+ }
+
+ final CellLayout.CellInfo dragCellInfo = mDragInfo;
+ final int spanX = dragCellInfo == null ? 1 : dragCellInfo.spanX;
+ final int spanY = dragCellInfo == null ? 1 : dragCellInfo.spanY;
- layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result);
- onDropExternal(result[0], result[1], info, layout, insertAtFirst);
+ final View ignoreView = dragCellInfo == null ? null : dragCellInfo.cell;
+
+ // Don't accept the drop if there's no room for the item
+ if (!mDragTargetLayout.findCellForSpanIgnoring(null, spanX, spanY, ignoreView)) {
+ mLauncher.showOutOfSpaceMessage();
+ return false;
+ }
+ }
+ return true;
}
public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
DragView dragView, Object dragInfo) {
- final CellLayout cellLayout = getCurrentDropLayout();
+
+ int originX = x - xOffset;
+ int originY = y - yOffset;
+
+ if (mIsSmall || mIsInUnshrinkAnimation) {
+ // get originX and originY in the local coordinate system of the screen
+ mTempOriginXY[0] = originX;
+ mTempOriginXY[1] = originY;
+ mapPointFromSelfToChild(mDragTargetLayout, mTempOriginXY);
+ originX = (int)mTempOriginXY[0];
+ originY = (int)mTempOriginXY[1];
+ }
+
if (source != this) {
- onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
+ onDropExternal(originX, originY, dragInfo, mDragTargetLayout);
+ } else if (mDragInfo != null) {
+ final View cell = mDragInfo.cell;
+ if (mDragTargetLayout != null) {
+ // Move internally
+ mTargetCell = findNearestVacantArea(originX, originY,
+ mDragInfo.spanX, mDragInfo.spanY, cell, mDragTargetLayout,
+ mTargetCell);
+
+ if (mTargetCell == null) {
+ snapToPage(mDragInfo.screen);
+ } else {
+ int screen = indexOfChild(mDragTargetLayout);
+ if (screen != mDragInfo.screen) {
+ // Reparent the view
+ ((CellLayout) getChildAt(mDragInfo.screen)).removeView(cell);
+ addInScreen(cell, screen, mTargetCell[0], mTargetCell[1],
+ mDragInfo.spanX, mDragInfo.spanY);
+ }
+
+ // update the item's position after drop
+ final ItemInfo info = (ItemInfo) cell.getTag();
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
+ mDragTargetLayout.onMove(cell, mTargetCell[0], mTargetCell[1]);
+ lp.cellX = mTargetCell[0];
+ lp.cellY = mTargetCell[1];
+ cell.setId(LauncherModel.getCellLayoutChildId(-1, mDragInfo.screen,
+ mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
+
+ LauncherModel.moveItemInDatabase(mLauncher, info,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
+ lp.cellX, lp.cellY);
+ }
+ }
+
+ final CellLayout parent = (CellLayout) cell.getParent();
+
+ // Prepare it to be animated into its new position
+ // This must be called after the view has been re-parented
+ setPositionForDropAnimation(dragView, originX, originY, parent, cell);
+ parent.onDropChild(cell);
+ }
+ }
+
+ public void onDragEnter(DragSource source, int x, int y, int xOffset,
+ int yOffset, DragView dragView, Object dragInfo) {
+ mDragTargetLayout = null; // Reset the drag state
+
+ if (!mIsSmall) {
+ mDragTargetLayout = getCurrentDropLayout();
+ mDragTargetLayout.onDragEnter();
+ showOutlines();
+ mInDragMode = true;
+ CellLayout cl = (CellLayout) getChildAt(mCurrentPage);
+ cl.setBackgroundAlphaMultiplier(1.0f);
+ }
+ }
+
+ public DropTarget getDropTargetDelegate(DragSource source, int x, int y,
+ int xOffset, int yOffset, DragView dragView, Object dragInfo) {
+
+ if (mIsSmall || mIsInUnshrinkAnimation) {
+ // If we're shrunken, don't let anyone drag on folders/etc that are on the mini-screens
+ return null;
+ }
+ // We may need to delegate the drag to a child view. If a 1x1 item
+ // would land in a cell occupied by a DragTarget (e.g. a Folder),
+ // then drag events should be handled by that child.
+
+ ItemInfo item = (ItemInfo)dragInfo;
+ CellLayout currentLayout = getCurrentDropLayout();
+
+ int dragPointX, dragPointY;
+ if (item.spanX == 1 && item.spanY == 1) {
+ // For a 1x1, calculate the drop cell exactly as in onDragOver
+ dragPointX = x - xOffset;
+ dragPointY = y - yOffset;
} else {
- // Move internally
- if (mDragInfo != null) {
- final View cell = mDragInfo.cell;
- int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
- if (index != mDragInfo.screen) {
- final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
- originalCellLayout.removeView(cell);
- cellLayout.addView(cell);
+ // Otherwise, use the exact drag coordinates
+ dragPointX = x;
+ dragPointY = y;
+ }
+ dragPointX += mScrollX - currentLayout.getLeft();
+ dragPointY += mScrollY - currentLayout.getTop();
+
+ // If we are dragging over a cell that contains a DropTarget that will
+ // accept the drop, delegate to that DropTarget.
+ final int[] cellXY = mTempCell;
+ currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY);
+ View child = currentLayout.getChildAt(cellXY[0], cellXY[1]);
+ if (child instanceof DropTarget) {
+ DropTarget target = (DropTarget)child;
+ if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) {
+ return target;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Global drag and drop handler
+ */
+ @Override
+ public boolean onDragEvent(DragEvent event) {
+ final ClipDescription desc = event.getClipDescription();
+ final CellLayout layout = (CellLayout) getChildAt(mCurrentPage);
+ final int[] pos = new int[2];
+ layout.getLocationOnScreen(pos);
+ // We need to offset the drag coordinates to layout coordinate space
+ final int x = (int) event.getX() - pos[0];
+ final int y = (int) event.getY() - pos[1];
+
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ // Check if we have enough space on this screen to add a new shortcut
+ if (!layout.findCellForSpan(pos, 1, 1)) {
+ Toast.makeText(mContext, mContext.getString(R.string.out_of_space),
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+
+ // Create the drag outline
+ // We need to add extra padding to the bitmap to make room for the glow effect
+ final Canvas canvas = new Canvas();
+ final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS;
+ mDragOutline = createExternalDragOutline(canvas, bitmapPadding);
+
+ // Show the current page outlines to indicate that we can accept this drop
+ showOutlines();
+ layout.setHover(true);
+ layout.onDragEnter();
+ layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1);
+
+ return true;
+ case DragEvent.ACTION_DRAG_LOCATION:
+ // Visualize the drop location
+ layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1);
+ return true;
+ case DragEvent.ACTION_DROP:
+ // Check if we have enough space on this screen to add a new shortcut
+ if (!layout.findCellForSpan(pos, 1, 1)) {
+ Toast.makeText(mContext, mContext.getString(R.string.out_of_space),
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+
+ // Try and add any shortcuts
+ int newDropCount = 0;
+ final LauncherModel model = mLauncher.getModel();
+ final ClipData data = event.getClipData();
+
+ // We assume that the mime types are ordered in descending importance of
+ // representation. So we enumerate the list of mime types and alert the
+ // user if any widgets can handle the drop. Only the most preferred
+ // representation will be handled.
+ pos[0] = x;
+ pos[1] = y;
+ final int mimeTypeCount = desc.getMimeTypeCount();
+ for (int j = 0; j < mimeTypeCount; ++j) {
+ final String mimeType = desc.getMimeType(j);
+
+ if (mimeType.equals(InstallShortcutReceiver.SHORTCUT_MIMETYPE)) {
+ final Intent intent = data.getItem(j).getIntent();
+ Object info = model.infoFromShortcutIntent(mContext, intent, data.getIcon());
+ onDropExternal(x, y, info, layout);
+ } else {
+ final List<WidgetMimeTypeHandlerData> widgets =
+ model.resolveWidgetsForMimeType(mContext, mimeType);
+ final int numWidgets = widgets.size();
+
+ if (numWidgets == 0) {
+ continue;
+ } else if (numWidgets == 1) {
+ // If there is only one item, then go ahead and add and configure
+ // that widget
+ final AppWidgetProviderInfo widgetInfo = widgets.get(0).widgetInfo;
+ final PendingAddWidgetInfo createInfo =
+ new PendingAddWidgetInfo(widgetInfo, mimeType, data);
+ mLauncher.addAppWidgetFromDrop(createInfo, mCurrentPage, pos);
+ } else if (numWidgets > 1) {
+ // Show the widget picker dialog if there is more than one widget
+ // that can handle this data type
+ final InstallWidgetReceiver.WidgetListAdapter adapter =
+ new InstallWidgetReceiver.WidgetListAdapter(mLauncher, mimeType,
+ data, widgets, layout, mCurrentPage, pos);
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(mContext);
+ builder.setAdapter(adapter, adapter);
+ builder.setCancelable(true);
+ builder.setTitle(mContext.getString(
+ R.string.external_drop_widget_pick_title));
+ builder.setIcon(R.drawable.ic_no_applications);
+ builder.show();
+ }
}
- mTargetCell = estimateDropCell(x - xOffset, y - yOffset,
- mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, mTargetCell);
- cellLayout.onDropChild(cell, mTargetCell);
-
- final ItemInfo info = (ItemInfo) cell.getTag();
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
- LauncherModel.moveItemInDatabase(mLauncher, info,
- LauncherSettings.Favorites.CONTAINER_DESKTOP, index, lp.cellX, lp.cellY);
+ newDropCount++;
+ break;
+ }
+
+ // Show error message if we couldn't accept any of the items
+ if (newDropCount <= 0) {
+ Toast.makeText(mContext, mContext.getString(R.string.external_drop_widget_error),
+ Toast.LENGTH_SHORT).show();
}
+
+ return true;
+ case DragEvent.ACTION_DRAG_ENDED:
+ // Hide the page outlines after the drop
+ layout.setHover(false);
+ layout.onDragExit();
+ hideOutlines();
+ return true;
+ }
+ return super.onDragEvent(event);
+ }
+
+ /*
+ *
+ * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
+ * coordinate space. The argument xy is modified with the return result.
+ *
+ */
+ void mapPointFromSelfToChild(View v, float[] xy) {
+ mapPointFromSelfToChild(v, xy, null);
+ }
+
+ /*
+ *
+ * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
+ * coordinate space. The argument xy is modified with the return result.
+ *
+ * if cachedInverseMatrix is not null, this method will just use that matrix instead of
+ * computing it itself; we use this to avoid redundant matrix inversions in
+ * findMatchingPageForDragOver
+ *
+ */
+ void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
+ if (cachedInverseMatrix == null) {
+ v.getMatrix().invert(mTempInverseMatrix);
+ cachedInverseMatrix = mTempInverseMatrix;
+ }
+ xy[0] = xy[0] + mScrollX - v.getLeft();
+ xy[1] = xy[1] + mScrollY - v.getTop();
+ cachedInverseMatrix.mapPoints(xy);
+ }
+
+ /*
+ *
+ * Convert the 2D coordinate xy from this CellLayout's coordinate space to
+ * the parent View's coordinate space. The argument xy is modified with the return result.
+ *
+ */
+ void mapPointFromChildToSelf(View v, float[] xy) {
+ v.getMatrix().mapPoints(xy);
+ xy[0] -= (mScrollX - v.getLeft());
+ xy[1] -= (mScrollY - v.getTop());
+ }
+
+ static private float squaredDistance(float[] point1, float[] point2) {
+ float distanceX = point1[0] - point2[0];
+ float distanceY = point2[1] - point2[1];
+ return distanceX * distanceX + distanceY * distanceY;
+ }
+
+ /*
+ *
+ * Returns true if the passed CellLayout cl overlaps with dragView
+ *
+ */
+ boolean overlaps(CellLayout cl, DragView dragView,
+ int dragViewX, int dragViewY, Matrix cachedInverseMatrix) {
+ // Transform the coordinates of the item being dragged to the CellLayout's coordinates
+ final float[] draggedItemTopLeft = mTempDragCoordinates;
+ draggedItemTopLeft[0] = dragViewX + dragView.getScaledDragRegionXOffset();
+ draggedItemTopLeft[1] = dragViewY + dragView.getScaledDragRegionYOffset();
+ final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates;
+ draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getScaledDragRegionWidth();
+ draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getScaledDragRegionHeight();
+
+ // Transform the dragged item's top left coordinates
+ // to the CellLayout's local coordinates
+ mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix);
+ float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]);
+ float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]);
+
+ if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) {
+ // Transform the dragged item's bottom right coordinates
+ // to the CellLayout's local coordinates
+ mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix);
+ float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]);
+ float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]);
+
+ if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) {
+ float overlap = (overlapRegionRight - overlapRegionLeft) *
+ (overlapRegionBottom - overlapRegionTop);
+ if (overlap > 0) {
+ return true;
+ }
+ }
}
+ return false;
}
- public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
- DragView dragView, Object dragInfo) {
- clearVacantCache();
+ /*
+ *
+ * This method returns the CellLayout that is currently being dragged to. In order to drag
+ * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
+ * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
+ *
+ * Return null if no CellLayout is currently being dragged over
+ *
+ */
+ private CellLayout findMatchingPageForDragOver(
+ DragView dragView, int originX, int originY, int offsetX, int offsetY) {
+ // We loop through all the screens (ie CellLayouts) and see which ones overlap
+ // with the item being dragged and then choose the one that's closest to the touch point
+ final int screenCount = getChildCount();
+ CellLayout bestMatchingScreen = null;
+ float smallestDistSoFar = Float.MAX_VALUE;
+
+ for (int i = 0; i < screenCount; i++) {
+ CellLayout cl = (CellLayout)getChildAt(i);
+
+ final float[] touchXy = mTempTouchCoordinates;
+ touchXy[0] = originX + offsetX;
+ touchXy[1] = originY + offsetY;
+
+ // Transform the touch coordinates to the CellLayout's local coordinates
+ // If the touch point is within the bounds of the cell layout, we can return immediately
+ cl.getMatrix().invert(mTempInverseMatrix);
+ mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
+
+ if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
+ touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
+ return cl;
+ }
+
+ if (overlaps(cl, dragView, originX, originY, mTempInverseMatrix)) {
+ // Get the center of the cell layout in screen coordinates
+ final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
+ cellLayoutCenter[0] = cl.getWidth()/2;
+ cellLayoutCenter[1] = cl.getHeight()/2;
+ mapPointFromChildToSelf(cl, cellLayoutCenter);
+
+ touchXy[0] = originX + offsetX;
+ touchXy[1] = originY + offsetY;
+
+ // Calculate the distance between the center of the CellLayout
+ // and the touch point
+ float dist = squaredDistance(touchXy, cellLayoutCenter);
+
+ if (dist < smallestDistSoFar) {
+ smallestDistSoFar = dist;
+ bestMatchingScreen = cl;
+ }
+ }
+ }
+ return bestMatchingScreen;
}
public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
DragView dragView, Object dragInfo) {
+ // When touch is inside the scroll area, skip dragOver actions for the current screen
+ if (!mInScrollArea) {
+ CellLayout layout;
+ int originX = x - xOffset;
+ int originY = y - yOffset;
+ if (mIsSmall || mIsInUnshrinkAnimation) {
+ layout = findMatchingPageForDragOver(
+ dragView, originX, originY, xOffset, yOffset);
+
+ if (layout != mDragTargetLayout) {
+ if (mDragTargetLayout != null) {
+ mDragTargetLayout.setHover(false);
+ }
+ mDragTargetLayout = layout;
+ if (mDragTargetLayout != null) {
+ mDragTargetLayout.setHover(true);
+ }
+ }
+ } else {
+ layout = getCurrentDropLayout();
+
+ final ItemInfo item = (ItemInfo)dragInfo;
+ if (dragInfo instanceof LauncherAppWidgetInfo) {
+ LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo)dragInfo;
+
+ if (widgetInfo.spanX == -1) {
+ // Calculate the grid spans needed to fit this widget
+ int[] spans = layout.rectToCell(
+ widgetInfo.minWidth, widgetInfo.minHeight, null);
+ item.spanX = spans[0];
+ item.spanY = spans[1];
+ }
+ }
+
+ if (source instanceof AllAppsPagedView) {
+ // This is a hack to fix the point used to determine which cell an icon from
+ // the all apps screen is over
+ if (item != null && item.spanX == 1 && layout != null) {
+ int dragRegionLeft = (dragView.getWidth() - layout.getCellWidth()) / 2;
+
+ originX += dragRegionLeft - dragView.getDragRegionLeft();
+ if (dragView.getDragRegionWidth() != layout.getCellWidth()) {
+ dragView.setDragRegion(dragView.getDragRegionLeft(),
+ dragView.getDragRegionTop(),
+ layout.getCellWidth(),
+ dragView.getDragRegionHeight());
+ }
+ }
+ }
+
+ if (layout != mDragTargetLayout) {
+ if (mDragTargetLayout != null) {
+ mDragTargetLayout.onDragExit();
+ }
+ layout.onDragEnter();
+ mDragTargetLayout = layout;
+ }
+
+ // only visualize the drop locations for moving icons within the home screen on
+ // tablet on phone, we also visualize icons dragged in from All Apps
+ if ((!LauncherApplication.isScreenXLarge() || source == this)
+ && mDragTargetLayout != null) {
+ final View child = (mDragInfo == null) ? null : mDragInfo.cell;
+ int localOriginX = originX - (mDragTargetLayout.getLeft() - mScrollX);
+ int localOriginY = originY - (mDragTargetLayout.getTop() - mScrollY);
+ mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
+ localOriginX, localOriginY, item.spanX, item.spanY);
+ }
+ }
+ }
}
- public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
- DragView dragView, Object dragInfo) {
- clearVacantCache();
+ public void onDragExit(DragSource source, int x, int y, int xOffset,
+ int yOffset, DragView dragView, Object dragInfo) {
+ if (mDragTargetLayout != null) {
+ mDragTargetLayout.onDragExit();
+ }
+ if (!mIsPageMoving) {
+ hideOutlines();
+ mInDragMode = false;
+ }
+ clearAllHovers();
}
- private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
+ private void onDropExternal(int x, int y, Object dragInfo,
+ CellLayout cellLayout) {
onDropExternal(x, y, dragInfo, cellLayout, false);
}
-
- private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout,
- boolean insertAtFirst) {
- // Drag from somewhere else
+
+ /**
+ * Add the item specified by dragInfo to the given layout.
+ * This is basically the equivalent of onDropExternal, except it's not initiated
+ * by drag and drop.
+ * @return true if successful
+ */
+ public boolean addExternalItemToScreen(Object dragInfo, View layout) {
+ CellLayout cl = (CellLayout) layout;
+ ItemInfo info = (ItemInfo) dragInfo;
+
+ if (cl.findCellForSpan(mTempEstimate, info.spanX, info.spanY)) {
+ onDropExternal(-1, -1, dragInfo, cl, false);
+ return true;
+ }
+ mLauncher.showOutOfSpaceMessage();
+ return false;
+ }
+
+ // Drag from somewhere else
+ // NOTE: This can also be called when we are outside of a drag event, when we want
+ // to add an item to one of the workspace screens.
+ private void onDropExternal(int x, int y, Object dragInfo,
+ CellLayout cellLayout, boolean insertAtFirst) {
+ int screen = indexOfChild(cellLayout);
+ if (dragInfo instanceof PendingAddItemInfo) {
+ PendingAddItemInfo info = (PendingAddItemInfo) dragInfo;
+ // When dragging and dropping from customization tray, we deal with creating
+ // widgets/shortcuts/folders in a slightly different way
+ int[] touchXY = new int[2];
+ touchXY[0] = x;
+ touchXY[1] = y;
+ switch (info.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) info, screen, touchXY);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
+ mLauncher.addLiveFolderFromDrop(info.componentName, screen, touchXY);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ mLauncher.processShortcutFromDrop(info.componentName, screen, touchXY);
+ break;
+ default:
+ throw new IllegalStateException("Unknown item type: " + info.itemType);
+ }
+ cellLayout.onDragExit();
+ cellLayout.animateDrop();
+ return;
+ }
+
+ // This is for other drag/drop cases, like dragging from All Apps
ItemInfo info = (ItemInfo) dragInfo;
- View view;
+ View view = null;
switch (info.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
if (info.container == NO_ID && info instanceof ApplicationInfo) {
// Came from all apps -- make a copy
- info = new ShortcutInfo((ApplicationInfo)info);
+ info = new ShortcutInfo((ApplicationInfo) info);
}
- view = mLauncher.createShortcut(R.layout.application, cellLayout, (ShortcutInfo)info);
+ view = mLauncher.createShortcut(R.layout.application, cellLayout,
+ (ShortcutInfo) info);
break;
case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
- (ViewGroup) getChildAt(mCurrentScreen), ((UserFolderInfo) info));
+ cellLayout, (UserFolderInfo) info, mIconCache);
break;
default:
throw new IllegalStateException("Unknown item type: " + info.itemType);
}
- cellLayout.addView(view, insertAtFirst ? 0 : -1);
- view.setHapticFeedbackEnabled(false);
- view.setOnLongClickListener(mLongClickListener);
- if (view instanceof DropTarget) {
- mDragController.addDropTarget((DropTarget) view);
- }
-
- mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell);
- cellLayout.onDropChild(view, mTargetCell);
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
+ // If the view is null, it has already been added.
+ if (view == null) {
+ cellLayout.onDragExit();
+ } else {
+ mTargetCell = findNearestVacantArea(x, y, 1, 1, null, cellLayout, mTargetCell);
+ addInScreen(view, indexOfChild(cellLayout), mTargetCell[0],
+ mTargetCell[1], info.spanX, info.spanY, insertAtFirst);
+ cellLayout.onDropChild(view);
+ cellLayout.animateDrop();
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
- LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
- LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
+ LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
+ lp.cellX, lp.cellY);
+ }
}
-
+
/**
* Return the current {@link CellLayout}, correctly picking the destination
* screen while a scroll is in progress.
*/
private CellLayout getCurrentDropLayout() {
- int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
+ // if we're currently small, use findMatchingPageForDragOver instead
+ if (mIsSmall) return null;
+ int index = mScroller.isFinished() ? mCurrentPage : mNextPage;
return (CellLayout) getChildAt(index);
}
/**
- * {@inheritDoc}
+ * Return the current CellInfo describing our current drag; this method exists
+ * so that Launcher can sync this object with the correct info when the activity is created/
+ * destroyed
+ *
*/
- public boolean acceptDrop(DragSource source, int x, int y,
- int xOffset, int yOffset, DragView dragView, Object dragInfo) {
- final CellLayout layout = getCurrentDropLayout();
- final CellLayout.CellInfo cellInfo = mDragInfo;
- final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
- final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
-
- if (mVacantCache == null) {
- final View ignoreView = cellInfo == null ? null : cellInfo.cell;
- mVacantCache = layout.findAllVacantCells(null, ignoreView);
- }
-
- return mVacantCache.findCellForSpan(mTempEstimate, spanX, spanY, false);
- }
-
- /**
- * {@inheritDoc}
- */
- public Rect estimateDropLocation(DragSource source, int x, int y,
- int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle) {
- final CellLayout layout = getCurrentDropLayout();
-
- final CellLayout.CellInfo cellInfo = mDragInfo;
- final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
- final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
- final View ignoreView = cellInfo == null ? null : cellInfo.cell;
-
- final Rect location = recycle != null ? recycle : new Rect();
-
- // Find drop cell and convert into rectangle
- int[] dropCell = estimateDropCell(x - xOffset, y - yOffset,
- spanX, spanY, ignoreView, layout, mTempCell);
-
- if (dropCell == null) {
- return null;
- }
-
- layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate);
- location.left = mTempEstimate[0];
- location.top = mTempEstimate[1];
-
- layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate);
- location.right = mTempEstimate[0];
- location.bottom = mTempEstimate[1];
-
- return location;
+ public CellLayout.CellInfo getDragInfo() {
+ return mDragInfo;
}
/**
* Calculate the nearest cell where the given object would be dropped.
*/
- private int[] estimateDropCell(int pixelX, int pixelY,
+ private int[] findNearestVacantArea(int pixelX, int pixelY,
int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
- // Create vacant cell cache if none exists
- if (mVacantCache == null) {
- mVacantCache = layout.findAllVacantCells(null, ignoreView);
- }
+
+ int localPixelX = pixelX - (layout.getLeft() - mScrollX);
+ int localPixelY = pixelY - (layout.getTop() - mScrollY);
// Find the best target drop location
- return layout.findNearestVacantArea(pixelX, pixelY,
- spanX, spanY, mVacantCache, recycle);
+ return layout.findNearestVacantArea(
+ localPixelX, localPixelY, spanX, spanY, ignoreView, recycle);
}
-
+
+ /**
+ * Estimate the size that a child with the given dimensions will take in the current screen.
+ */
+ void estimateChildSize(int minWidth, int minHeight, int[] result) {
+ ((CellLayout)getChildAt(mCurrentPage)).estimateChildSize(minWidth, minHeight, result);
+ }
+
void setLauncher(Launcher launcher) {
mLauncher = launcher;
}
@@ -1215,61 +1885,80 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
}
public void onDropCompleted(View target, boolean success) {
- clearVacantCache();
-
- if (success){
+ if (success) {
if (target != this && mDragInfo != null) {
final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
cellLayout.removeView(mDragInfo.cell);
if (mDragInfo.cell instanceof DropTarget) {
mDragController.removeDropTarget((DropTarget)mDragInfo.cell);
}
- //final Object tag = mDragInfo.cell.getTag();
- }
- } else {
- if (mDragInfo != null) {
- final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
- cellLayout.onDropAborted(mDragInfo.cell);
+ // final Object tag = mDragInfo.cell.getTag();
}
+ } else if (mDragInfo != null) {
+ ((CellLayout) getChildAt(mDragInfo.screen)).onDropChild(mDragInfo.cell);
}
+ mDragOutline = null;
mDragInfo = null;
}
+ public boolean isDropEnabled() {
+ return true;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ super.onRestoreInstanceState(state);
+ Launcher.setScreen(mCurrentPage);
+ }
+
+ @Override
public void scrollLeft() {
- clearVacantCache();
- if (mScroller.isFinished()) {
- if (mCurrentScreen > 0) snapToScreen(mCurrentScreen - 1);
- } else {
- if (mNextScreen > 0) snapToScreen(mNextScreen - 1);
+ if (!mIsSmall && !mIsInUnshrinkAnimation) {
+ super.scrollLeft();
}
}
+ @Override
public void scrollRight() {
- clearVacantCache();
- if (mScroller.isFinished()) {
- if (mCurrentScreen < getChildCount() -1) snapToScreen(mCurrentScreen + 1);
- } else {
- if (mNextScreen < getChildCount() -1) snapToScreen(mNextScreen + 1);
+ if (!mIsSmall && !mIsInUnshrinkAnimation) {
+ super.scrollRight();
}
}
- public int getScreenForView(View v) {
- int result = -1;
- if (v != null) {
- ViewParent vp = v.getParent();
- int count = getChildCount();
- for (int i = 0; i < count; i++) {
- if (vp == getChildAt(i)) {
- return i;
+ @Override
+ public void onEnterScrollArea(int direction) {
+ if (!mIsSmall && !mIsInUnshrinkAnimation) {
+ mInScrollArea = true;
+ final int screen = getCurrentPage() + ((direction == DragController.SCROLL_LEFT) ? -1 : 1);
+ if (0 <= screen && screen < getChildCount()) {
+ ((CellLayout) getChildAt(screen)).setHover(true);
+
+ if (mDragTargetLayout != null) {
+ mDragTargetLayout.onDragExit();
+ mDragTargetLayout = null;
}
}
}
- return result;
+ }
+
+ private void clearAllHovers() {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ ((CellLayout) getChildAt(i)).setHover(false);
+ }
+ }
+
+ @Override
+ public void onExitScrollArea() {
+ if (mInScrollArea) {
+ mInScrollArea = false;
+ clearAllHovers();
+ }
}
public Folder getFolderForTag(Object tag) {
- int screenCount = getChildCount();
+ final int screenCount = getChildCount();
for (int screen = 0; screen < screenCount; screen++) {
CellLayout currentScreen = ((CellLayout) getChildAt(screen));
int count = currentScreen.getChildCount();
@@ -1278,7 +1967,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
Folder f = (Folder) child;
- if (f.getInfo() == tag) {
+ if (f.getInfo() == tag && f.getInfo().opened) {
return f;
}
}
@@ -1302,23 +1991,9 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
return null;
}
- /**
- * @return True is long presses are still allowed for the current touch
- */
- public boolean allowLongPress() {
- return mAllowLongPress;
- }
-
- /**
- * Set true to allow long-press events to be triggered, usually checked by
- * {@link Launcher} to accept or block dpad-initiated long-presses.
- */
- public void setAllowLongPress(boolean allowLongPress) {
- mAllowLongPress = allowLongPress;
- }
void removeItems(final ArrayList<ApplicationInfo> apps) {
- final int count = getChildCount();
+ final int screenCount = getChildCount();
final PackageManager manager = getContext().getPackageManager();
final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext());
@@ -1328,7 +2003,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
packageNames.add(apps.get(i).componentName.getPackageName());
}
- for (int i = 0; i < count; i++) {
+ for (int i = 0; i < screenCount; i++) {
final CellLayout layout = (CellLayout) getChildAt(i);
// Avoid ANRs by treating each screen separately
@@ -1336,17 +2011,17 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
public void run() {
final ArrayList<View> childrenToRemove = new ArrayList<View>();
childrenToRemove.clear();
-
+
int childCount = layout.getChildCount();
for (int j = 0; j < childCount; j++) {
final View view = layout.getChildAt(j);
Object tag = view.getTag();
-
+
if (tag instanceof ShortcutInfo) {
final ShortcutInfo info = (ShortcutInfo) tag;
final Intent intent = info.intent;
final ComponentName name = intent.getComponent();
-
+
if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
for (String packageName: packageNames) {
if (packageName.equals(name.getPackageName())) {
@@ -1361,12 +2036,12 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1);
final int contentsCount = contents.size();
boolean removedFromFolder = false;
-
+
for (int k = 0; k < contentsCount; k++) {
final ShortcutInfo appInfo = contents.get(k);
final Intent intent = appInfo.intent;
final ComponentName name = intent.getComponent();
-
+
if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
for (String packageName: packageNames) {
if (packageName.equals(name.getPackageName())) {
@@ -1377,11 +2052,12 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
}
}
}
-
+
contents.removeAll(toRemove);
if (removedFromFolder) {
final Folder folder = getOpenFolder();
- if (folder != null) folder.notifyDataSetChanged();
+ if (folder != null)
+ folder.notifyDataSetChanged();
}
} else if (tag instanceof LiveFolderInfo) {
final LiveFolderInfo info = (LiveFolderInfo) tag;
@@ -1393,7 +2069,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
for (String packageName: packageNames) {
if (packageName.equals(providerInfo.packageName)) {
LauncherModel.deleteItemFromDatabase(mLauncher, info);
- childrenToRemove.add(view);
+ childrenToRemove.add(view);
}
}
}
@@ -1405,13 +2081,13 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
for (String packageName: packageNames) {
if (packageName.equals(provider.provider.getPackageName())) {
LauncherModel.deleteItemFromDatabase(mLauncher, info);
- childrenToRemove.add(view);
+ childrenToRemove.add(view);
}
}
}
}
}
-
+
childCount = childrenToRemove.size();
for (int j = 0; j < childCount; j++) {
View child = childrenToRemove.get(j);
@@ -1420,7 +2096,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
mDragController.removeDropTarget((DropTarget)child);
}
}
-
+
if (childCount > 0) {
layout.requestLayout();
layout.invalidate();
@@ -1431,10 +2107,8 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
}
void updateShortcuts(ArrayList<ApplicationInfo> apps) {
- final PackageManager pm = mLauncher.getPackageManager();
-
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
+ final int screenCount = getChildCount();
+ for (int i = 0; i < screenCount; i++) {
final CellLayout layout = (CellLayout) getChildAt(i);
int childCount = layout.getChildCount();
for (int j = 0; j < childCount; j++) {
@@ -1450,7 +2124,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
final int appCount = apps.size();
- for (int k=0; k<appCount; k++) {
+ for (int k = 0; k < appCount; k++) {
ApplicationInfo app = apps.get(k);
if (app.componentName.equals(name)) {
info.setIcon(mIconCache.getIcon(info.intent));
@@ -1466,48 +2140,29 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
}
void moveToDefaultScreen(boolean animate) {
- if (animate) {
- snapToScreen(mDefaultScreen);
+ if (mIsSmall || mIsInUnshrinkAnimation) {
+ mLauncher.showWorkspace(animate, (CellLayout)getChildAt(mDefaultPage));
+ } else if (animate) {
+ snapToPage(mDefaultPage);
} else {
- setCurrentScreen(mDefaultScreen);
+ setCurrentPage(mDefaultPage);
}
- getChildAt(mDefaultScreen).requestFocus();
+ getChildAt(mDefaultPage).requestFocus();
}
void setIndicators(Drawable previous, Drawable next) {
mPreviousIndicator = previous;
mNextIndicator = next;
- previous.setLevel(mCurrentScreen);
- next.setLevel(mCurrentScreen);
+ previous.setLevel(mCurrentPage);
+ next.setLevel(mCurrentPage);
}
- public static class SavedState extends BaseSavedState {
- int currentScreen = -1;
-
- SavedState(Parcelable superState) {
- super(superState);
- }
-
- private SavedState(Parcel in) {
- super(in);
- currentScreen = in.readInt();
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- super.writeToParcel(out, flags);
- out.writeInt(currentScreen);
- }
-
- public static final Parcelable.Creator<SavedState> CREATOR =
- new Parcelable.Creator<SavedState>() {
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
+ @Override
+ public void syncPages() {
+ }
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
+ @Override
+ public void syncPageItems(int page) {
}
+
}
diff --git a/src/com/android/launcher2/allapps.rs b/src/com/android/launcher2/allapps.rs
new file mode 100644
index 000000000..a9727fb95
--- /dev/null
+++ b/src/com/android/launcher2/allapps.rs
@@ -0,0 +1,389 @@
+#pragma version(1)
+
+#pragma rs java_package_name(com.android.launcher2)
+
+#include "rs_graphics.rsh"
+
+#define PI 3.14159f
+
+// Constants from Java
+int COLUMNS_PER_PAGE_PORTRAIT;
+int ROWS_PER_PAGE_PORTRAIT;
+int COLUMNS_PER_PAGE_LANDSCAPE;
+int ROWS_PER_PAGE_LANDSCAPE;
+
+int gIconCount;
+int gSelectedIconIndex = -1;
+rs_allocation gSelectedIconTexture;
+rs_allocation gHomeButton;
+
+rs_program_fragment gPFTexNearest;
+rs_program_fragment gPFTexMip;
+rs_program_fragment gPFTexMipAlpha;
+rs_program_vertex gPVCurve;
+rs_program_store gPS;
+rs_mesh gSMCell;
+
+rs_allocation *gIconIDs;
+rs_allocation *gLabelIDs;
+
+typedef struct VpConsts {
+ rs_matrix4x4 Proj;
+ float4 Position;
+ float4 ScaleOffset;
+ float2 BendPos;
+ float2 ImgSize;
+} VpConsts_t;
+VpConsts_t *vpConstants;
+
+
+#pragma rs export_func(move, moveTo, setZoom, fling)
+
+
+// Attraction to center values from page edge to page center.
+static float g_AttractionTable[9] = {20.f, 20.f, 20.f, 10.f, -10.f, -20.f, -20.f, -20.f, -20.f};
+static float g_FrictionTable[9] = {10.f, 10.f, 11.f, 15.f, 15.f, 11.f, 10.f, 10.f, 10.f};
+static float g_PhysicsTableSize = 7;
+
+static float gZoomTarget;
+float gTargetPos;
+static float g_PosPage = 0.f;
+static float g_PosVelocity = 0.f;
+static float g_LastPositionX = 0.f;
+static bool g_LastTouchDown = false;
+static float g_DT;
+static int g_PosMax;
+static float g_Zoom = 0.f;
+static float g_Animation = 1.f;
+static float g_OldPosPage;
+static float g_OldPosVelocity;
+static float g_OldZoom;
+static float g_MoveToTotalTime = 0.2f;
+static float g_MoveToTime = 0.f;
+static float g_MoveToOldPos = 0.f;
+
+static int g_Cols;
+static int g_Rows;
+
+rs_allocation g_VPConstAlloc;
+
+// Drawing constants, should be parameters ======
+#define VIEW_ANGLE 1.28700222f
+
+
+static void updateReadback() {
+ if ((g_OldPosPage != g_PosPage) ||
+ (g_OldPosVelocity != g_PosVelocity) ||
+ (g_OldZoom != g_Zoom)) {
+
+ g_OldPosPage = g_PosPage;
+ g_OldPosVelocity = g_PosVelocity;
+ g_OldZoom = g_Zoom;
+
+ int i[3];
+ i[0] = g_PosPage * (1 << 16);
+ i[1] = g_PosVelocity * (1 << 16);
+ i[2] = g_OldZoom * (1 << 16);
+ rsSendToClientBlocking(1, &i[0], sizeof(i));
+ }
+}
+
+void init() {
+}
+
+void move(float newPos) {
+ if (g_LastTouchDown) {
+ float dx = -(newPos - g_LastPositionX);
+ g_PosVelocity = 0;
+ g_PosPage += dx * 5.2f;
+
+ float pmin = -0.49f;
+ float pmax = g_PosMax + 0.49f;
+ g_PosPage = clamp(g_PosPage, pmin, pmax);
+ }
+ g_LastTouchDown = true;
+ g_LastPositionX = newPos;
+ g_MoveToTime = 0;
+}
+
+void moveTo(float targetPos) {
+ gTargetPos = targetPos;
+ g_MoveToTime = g_MoveToTotalTime;
+ g_PosVelocity = 0;
+ g_MoveToOldPos = g_PosPage;
+}
+
+void setZoom(float z, /*bool*/ int animate) {
+ gZoomTarget = z;
+ if (gZoomTarget < 0.001f) {
+ gZoomTarget = 0;
+ }
+ if (!animate) {
+ g_Zoom = gZoomTarget;
+ }
+ updateReadback();
+}
+
+void fling(float newPos, float vel) {
+ move(newPos);
+
+ g_LastTouchDown = false;
+ g_PosVelocity = -vel * 4;
+ float av = fabs(g_PosVelocity);
+ float minVel = 3.5f;
+
+ minVel *= 1.f - (fabs(rsFrac(g_PosPage + 0.5f) - 0.5f) * 0.45f);
+
+ if (av < minVel && av > 0.2f) {
+ if (g_PosVelocity > 0) {
+ g_PosVelocity = minVel;
+ } else {
+ g_PosVelocity = -minVel;
+ }
+ }
+
+ if (g_PosPage <= 0) {
+ g_PosVelocity = max(0.f, g_PosVelocity);
+ }
+ if (g_PosPage > g_PosMax) {
+ g_PosVelocity = min(0.f, g_PosVelocity);
+ }
+}
+
+// Interpolates values in the range 0..1 to a curve that eases in
+// and out.
+static float getInterpolation(float input) {
+ return (cos((input + 1) * PI) * 0.5f) + 0.5f;
+}
+
+
+static void updatePos() {
+ if (g_LastTouchDown) {
+ return;
+ }
+
+ float tablePosNorm = rsFrac(g_PosPage + 0.5f);
+ float tablePosF = tablePosNorm * g_PhysicsTableSize;
+ int tablePosI = tablePosF;
+ float tablePosFrac = tablePosF - tablePosI;
+ float accel = mix(g_AttractionTable[tablePosI],
+ g_AttractionTable[tablePosI + 1],
+ tablePosFrac) * g_DT;
+ float friction = mix(g_FrictionTable[tablePosI],
+ g_FrictionTable[tablePosI + 1],
+ tablePosFrac) * g_DT;
+
+ if (g_MoveToTime) {
+ // New position is old posiition + (total distance) * (interpolated time)
+ g_PosPage = g_MoveToOldPos + (gTargetPos - g_MoveToOldPos) * getInterpolation((g_MoveToTotalTime - g_MoveToTime) / g_MoveToTotalTime);
+ g_MoveToTime -= g_DT;
+ if (g_MoveToTime <= 0) {
+ g_MoveToTime = 0;
+ g_PosPage = gTargetPos;
+ }
+ return;
+ }
+
+ // If our velocity is low OR acceleration is opposing it, apply it.
+ if (fabs(g_PosVelocity) < 4.0f || (g_PosVelocity * accel) < 0) {
+ g_PosVelocity += accel;
+ }
+ //RS_DEBUG(g_PosPage);
+ //RS_DEBUG(g_PosVelocity);
+ //RS_DEBUG(friction);
+ //RS_DEBUG(accel);
+
+ // Normal physics
+ if (g_PosVelocity > 0) {
+ g_PosVelocity -= friction;
+ g_PosVelocity = max(g_PosVelocity, 0.f);
+ } else {
+ g_PosVelocity += friction;
+ g_PosVelocity = min(g_PosVelocity, 0.f);
+ }
+
+ if ((friction > fabs(g_PosVelocity)) && (friction > fabs(accel))) {
+ // Special get back to center and overcome friction physics.
+ float t = tablePosNorm - 0.5f;
+ if (fabs(t) < (friction * g_DT)) {
+ // really close, just snap
+ g_PosPage = round(g_PosPage);
+ g_PosVelocity = 0;
+ } else {
+ if (t > 0) {
+ g_PosVelocity = -friction;
+ } else {
+ g_PosVelocity = friction;
+ }
+ }
+ }
+
+ // Check for out of boundry conditions.
+ if (g_PosPage < 0 && g_PosVelocity < 0) {
+ float damp = 1.0f + (g_PosPage * 4);
+ damp = clamp(damp, 0.f, 0.9f);
+ g_PosVelocity *= damp;
+ }
+ if (g_PosPage > g_PosMax && g_PosVelocity > 0) {
+ float damp = 1.0f - ((g_PosPage - g_PosMax) * 4);
+ damp = clamp(damp, 0.f, 0.9f);
+ g_PosVelocity *= damp;
+ }
+
+ g_PosPage += g_PosVelocity * g_DT;
+ g_PosPage = clamp(g_PosPage, -0.49f, g_PosMax + 0.49f);
+}
+
+static void
+draw_home_button()
+{
+ rsgBindTexture(gPFTexNearest, 0, gHomeButton);
+
+ float w = rsgGetWidth();
+ float h = rsgGetHeight();
+ float tw = rsAllocationGetDimX(gHomeButton);
+ float th = rsAllocationGetDimY(gHomeButton);
+
+ float x;
+ float y;
+ if (w > h) {
+ x = w - (tw * (1 - g_Animation)) + 20;
+ y = (h - th) * 0.5f;
+ } else {
+ x = (w - tw) / 2;
+ y = -g_Animation * th;
+ y -= 30; // move the house to the edge of the screen as it doesn't fill the texture.
+ }
+
+ rsgDrawSpriteScreenspace(x, y, 0, tw, th);
+}
+
+static void drawFrontGrid(float rowOffset, float p)
+{
+ float h = rsgGetHeight();
+ float w = rsgGetWidth();
+
+ int intRowOffset = rowOffset;
+ float rowFrac = rowOffset - intRowOffset;
+ float colWidth = 120.f;//w / 4;
+ float rowHeight = colWidth + 25.f;
+ float yoff = 0.5f * h + 1.5f * rowHeight;
+
+ int row, col;
+ int colCount = 4;
+ if (w > h) {
+ colCount = 6;
+ rowHeight -= 12.f;
+ yoff = 0.47f * h + 1.0f * rowHeight;
+ }
+
+ int iconNum = (intRowOffset - 5) * colCount;
+
+ rsgBindProgramVertex(gPVCurve);
+
+ vpConstants->Position.z = p;
+
+ for (row = -5; row < 15; row++) {
+ float y = yoff - ((-rowFrac + row) * rowHeight);
+
+ for (col=0; col < colCount; col++) {
+ if (iconNum >= gIconCount) {
+ return;
+ }
+
+ if (iconNum >= 0) {
+ float x = colWidth * col + (colWidth / 2);
+ vpConstants->Position.x = x + 0.2f;
+
+ if (gSelectedIconIndex == iconNum && !p && rsIsObject(gSelectedIconTexture)) {
+ rsgBindProgramFragment(gPFTexNearest);
+ rsgBindTexture(gPFTexNearest, 0, gSelectedIconTexture);
+ vpConstants->ImgSize.x = rsAllocationGetDimX(gSelectedIconTexture);
+ vpConstants->ImgSize.y = rsAllocationGetDimY(gSelectedIconTexture);
+ vpConstants->Position.y = y - (rsAllocationGetDimY(gSelectedIconTexture)
+ - rsAllocationGetDimY(gIconIDs[iconNum])) * 0.5f;
+ rsAllocationMarkDirty(g_VPConstAlloc);
+ rsgDrawMesh(gSMCell);
+ }
+
+ rsgBindProgramFragment(gPFTexMip);
+ vpConstants->ImgSize.x = rsAllocationGetDimX(gIconIDs[iconNum]);
+ vpConstants->ImgSize.y = rsAllocationGetDimY(gIconIDs[iconNum]);
+ vpConstants->Position.y = y - 0.2f;
+ rsAllocationMarkDirty(g_VPConstAlloc);
+ rsgBindTexture(gPFTexMip, 0, gIconIDs[iconNum]);
+ rsgDrawMesh(gSMCell);
+
+ rsgBindProgramFragment(gPFTexMipAlpha);
+ vpConstants->ImgSize.x = rsAllocationGetDimX(gLabelIDs[iconNum]);
+ vpConstants->ImgSize.y = rsAllocationGetDimY(gLabelIDs[iconNum]);
+ vpConstants->Position.y = y - 64.f - 0.2f;
+ rsAllocationMarkDirty(g_VPConstAlloc);
+ rsgBindTexture(gPFTexMipAlpha, 0, gLabelIDs[iconNum]);
+ rsgDrawMesh(gSMCell);
+ }
+ iconNum++;
+ }
+ }
+}
+
+
+int root()
+{
+ // Compute dt in seconds.
+ // physics may break if DT is large.
+ g_DT = min(rsGetDt(), 0.1f);
+ g_VPConstAlloc = rsGetAllocation(vpConstants);
+
+ if (g_Zoom != gZoomTarget) {
+ float dz = g_DT * 1.7f;
+ if (gZoomTarget < 0.5f) {
+ dz = -dz;
+ }
+ if (fabs(g_Zoom - gZoomTarget) < fabs(dz)) {
+ g_Zoom = gZoomTarget;
+ } else {
+ g_Zoom += dz;
+ }
+ updateReadback();
+ }
+ g_Animation = pow(1.f - g_Zoom, 3.f);
+
+ // Set clear value to dim the background based on the zoom position.
+ if ((g_Zoom < 0.001f) && (gZoomTarget < 0.001f)) {
+ rsgClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ // When we're zoomed out and not tracking motion events, reset the pos to 0.
+ if (!g_LastTouchDown) {
+ g_PosPage = 0;
+ }
+ return 0;
+ } else {
+ rsgClearColor(0.0f, 0.0f, 0.0f, g_Zoom);
+ }
+
+ rsgBindProgramStore(gPS);
+
+ // icons & labels
+ if (rsgGetWidth() > rsgGetHeight()) {
+ g_Cols = COLUMNS_PER_PAGE_LANDSCAPE;
+ g_Rows = ROWS_PER_PAGE_LANDSCAPE;
+ } else {
+ g_Cols = COLUMNS_PER_PAGE_PORTRAIT;
+ g_Rows = ROWS_PER_PAGE_PORTRAIT;
+ }
+
+ g_PosMax = ((gIconCount + (g_Cols-1)) / g_Cols) - g_Rows;
+ if (g_PosMax < 0) g_PosMax = 0;
+
+ updatePos();
+ updateReadback();
+
+ // Draw the icons ========================================
+ drawFrontGrid(g_PosPage, g_Animation);
+
+ rsgBindProgramFragment(gPFTexNearest);
+ draw_home_button();
+ return (g_PosVelocity != 0) || rsFrac(g_PosPage) || g_Zoom != gZoomTarget || (g_MoveToTime != 0);
+}
+
+