summaryrefslogtreecommitdiffstats
path: root/src/com/android
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2015-09-14 21:55:16 +0000
committerAndroid Git Automerger <android-git-automerger@android.com>2015-09-14 21:55:16 +0000
commit54d17dff6db6847b33bf07e1af6fbaa2e3bbe19d (patch)
tree82db2e21449fd52665d7cff5f67b72ea655d185f /src/com/android
parentedd51ad30741098e2110e3d0533085b02cbe752c (diff)
parent00e827c70cb4213ee05140157006a37d12662448 (diff)
downloadandroid_packages_apps_Trebuchet-54d17dff6db6847b33bf07e1af6fbaa2e3bbe19d.tar.gz
android_packages_apps_Trebuchet-54d17dff6db6847b33bf07e1af6fbaa2e3bbe19d.tar.bz2
android_packages_apps_Trebuchet-54d17dff6db6847b33bf07e1af6fbaa2e3bbe19d.zip
am 00e827c7: am 5fcaab43: am ea9ad5ce: Merge remote-tracking branch \'origin/ub-launcher3-burnaby\' into mnc-dev
* commit '00e827c70cb4213ee05140157006a37d12662448': (76 commits) Restoring provider behavior for reloading app on old devices > For older devices, launcher will only reload in case of inserts with specific query parameters > For older devices, launcehr will notify content observers of any internal inserts > Chaning TAG for Launcher provider as max logging tag is only 23 characters Removing items which are on invalid screen Preventing null pointer crash when opening a folder Revert workaround for move to default screen on home intent. Fixing NPE in recycler view scroll bar. Adding workaround for regression caused by ag/752175 Adding gradle script for Android Studio Override the overscroll color for the widget rows. Adding graphic for all apps empty search screen. Using GET_UNINSTALLED_PACKAGES flag when getting packageInfo for a managed profile app Revert "Adding viewId for the QSB" Adding viewId for the QSB Fixing issue with missing scroll bar after fast-scrolling and searching. Fixing an issue where you would inadvertently start fastscrolling. Pending bind callbacks should be cleared before starting the loader, similar to startBinding Fixing widgets container inactive scroll bar color. Making the detached scrollbar catch up faster to the actual scroll position. Updating theme to use the light theme by default, instead of wallpaper theme > This allows us to use all the goodness of material theme > Cursor in folder edit text is no longer 1px wide Updating the target sdk to launcher Using the usermanager api to get creation time ...
Diffstat (limited to 'src/com/android')
-rw-r--r--src/com/android/launcher3/AppWidgetResizeFrame.java4
-rw-r--r--src/com/android/launcher3/AppWidgetsRestoredReceiver.java4
-rw-r--r--src/com/android/launcher3/AutoInstallsLayout.java4
-rw-r--r--src/com/android/launcher3/BaseContainerView.java22
-rw-r--r--src/com/android/launcher3/BaseRecyclerView.java40
-rw-r--r--src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java145
-rw-r--r--src/com/android/launcher3/BubbleTextView.java11
-rw-r--r--src/com/android/launcher3/ButtonDropTarget.java9
-rw-r--r--src/com/android/launcher3/CellLayout.java67
-rw-r--r--src/com/android/launcher3/DeviceProfile.java20
-rw-r--r--src/com/android/launcher3/DragLayer.java4
-rw-r--r--src/com/android/launcher3/DragView.java6
-rw-r--r--src/com/android/launcher3/ExtendedEditText.java (renamed from src/com/android/launcher3/allapps/AllAppsSearchEditView.java)20
-rw-r--r--src/com/android/launcher3/Folder.java21
-rw-r--r--src/com/android/launcher3/FolderEditText.java36
-rw-r--r--src/com/android/launcher3/FolderPagedView.java4
-rw-r--r--src/com/android/launcher3/IconCache.java47
-rw-r--r--src/com/android/launcher3/Launcher.java308
-rw-r--r--src/com/android/launcher3/LauncherAppState.java9
-rw-r--r--src/com/android/launcher3/LauncherAppWidgetHost.java2
-rw-r--r--src/com/android/launcher3/LauncherAppWidgetInfo.java4
-rw-r--r--src/com/android/launcher3/LauncherAppWidgetProviderInfo.java81
-rw-r--r--src/com/android/launcher3/LauncherBackupAgentHelper.java16
-rw-r--r--src/com/android/launcher3/LauncherBackupHelper.java98
-rw-r--r--src/com/android/launcher3/LauncherCallbacks.java1
-rw-r--r--src/com/android/launcher3/LauncherClings.java22
-rw-r--r--src/com/android/launcher3/LauncherModel.java223
-rw-r--r--src/com/android/launcher3/LauncherProvider.java41
-rw-r--r--src/com/android/launcher3/LauncherSettings.java2
-rw-r--r--src/com/android/launcher3/LauncherStateTransitionAnimation.java224
-rw-r--r--src/com/android/launcher3/LauncherViewPropertyAnimator.java4
-rw-r--r--src/com/android/launcher3/PagedView.java2
-rw-r--r--src/com/android/launcher3/PendingAppWidgetHostView.java200
-rw-r--r--src/com/android/launcher3/PreloadIconDrawable.java4
-rw-r--r--src/com/android/launcher3/SearchDropTargetBar.java216
-rw-r--r--src/com/android/launcher3/ShortcutInfo.java8
-rw-r--r--src/com/android/launcher3/StylusEventHelper.java8
-rw-r--r--src/com/android/launcher3/UninstallDropTarget.java2
-rw-r--r--src/com/android/launcher3/Utilities.java41
-rw-r--r--src/com/android/launcher3/WidgetPreviewLoader.java21
-rw-r--r--src/com/android/launcher3/Workspace.java87
-rw-r--r--src/com/android/launcher3/WorkspaceStateTransitionAnimation.java80
-rw-r--r--src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java194
-rw-r--r--src/com/android/launcher3/allapps/AllAppsContainerView.java36
-rw-r--r--src/com/android/launcher3/allapps/AllAppsGridAdapter.java160
-rw-r--r--src/com/android/launcher3/allapps/AllAppsRecyclerView.java154
-rw-r--r--src/com/android/launcher3/allapps/AlphabeticalAppsList.java98
-rw-r--r--src/com/android/launcher3/allapps/DefaultAppSearchController.java32
-rw-r--r--src/com/android/launcher3/compat/AppWidgetManagerCompat.java2
-rw-r--r--src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java6
-rw-r--r--src/com/android/launcher3/compat/LauncherAppsCompat.java2
-rw-r--r--src/com/android/launcher3/compat/LauncherAppsCompatV16.java3
-rw-r--r--src/com/android/launcher3/compat/PackageInstallerCompat.java2
-rw-r--r--src/com/android/launcher3/compat/UserHandleCompat.java10
-rw-r--r--src/com/android/launcher3/compat/UserManagerCompat.java25
-rw-r--r--src/com/android/launcher3/compat/UserManagerCompatV16.java4
-rw-r--r--src/com/android/launcher3/compat/UserManagerCompatV17.java33
-rw-r--r--src/com/android/launcher3/compat/UserManagerCompatVL.java34
-rw-r--r--src/com/android/launcher3/model/AbstractUserComparator.java22
-rw-r--r--src/com/android/launcher3/model/AppNameComparator.java2
-rw-r--r--src/com/android/launcher3/model/MigrateFromRestoreTask.java767
-rw-r--r--src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java85
-rw-r--r--src/com/android/launcher3/model/WidgetsModel.java33
-rw-r--r--src/com/android/launcher3/testing/LauncherExtension.java5
-rw-r--r--src/com/android/launcher3/util/ComponentKey.java7
-rw-r--r--src/com/android/launcher3/util/ManagedProfileHeuristic.java4
-rw-r--r--src/com/android/launcher3/util/UiThreadCircularReveal.java2
-rw-r--r--src/com/android/launcher3/util/WallpaperUtils.java4
-rw-r--r--src/com/android/launcher3/widget/PendingAddWidgetInfo.java16
-rw-r--r--src/com/android/launcher3/widget/WidgetCell.java4
-rw-r--r--src/com/android/launcher3/widget/WidgetHostViewLoader.java3
-rw-r--r--src/com/android/launcher3/widget/WidgetsContainerView.java1
-rw-r--r--src/com/android/launcher3/widget/WidgetsListAdapter.java5
-rw-r--r--src/com/android/launcher3/widget/WidgetsRecyclerView.java36
74 files changed, 2789 insertions, 1170 deletions
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index ea7c22189..e6bf52531 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -75,8 +75,8 @@ public class AppWidgetResizeFrame extends FrameLayout {
mResizeMode = info.resizeMode;
mDragLayer = dragLayer;
- mMinHSpan = info.getMinSpanX(mLauncher);
- mMinVSpan = info.getMinSpanY(mLauncher);
+ mMinHSpan = info.minSpanX;
+ mMinVSpan = info.minSpanY;
setBackgroundResource(R.drawable.widget_resize_shadow);
setForeground(getResources().getDrawable(R.drawable.widget_resize_frame));
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index 5e7a012d2..b1d51ece0 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -13,6 +13,7 @@ import android.os.AsyncTask;
import android.util.Log;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.compat.AppWidgetManagerCompat;
import java.util.ArrayList;
import java.util.List;
@@ -48,7 +49,8 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
final AppWidgetProviderInfo provider = widgets.getAppWidgetInfo(newWidgetIds[i]);
final int state;
if (LauncherModel.isValidProvider(provider)) {
- state = LauncherAppWidgetInfo.RESTORE_COMPLETED;
+ // This will ensure that we show 'Click to setup' UI if required.
+ state = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
} else {
state = LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
}
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 99a98ddac..440e4e7b9 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -355,7 +355,7 @@ public class AutoInstallsLayout {
return addShortcut(info.loadLabel(mPackageManager).toString(),
intent, Favorites.ITEM_TYPE_APPLICATION);
} catch (PackageManager.NameNotFoundException e) {
- if (LOGD) Log.w(TAG, "Unable to add favorite: " + packageName + "/" + className, e);
+ Log.e(TAG, "Unable to add favorite: " + packageName + "/" + className, e);
}
return -1;
} else {
@@ -367,7 +367,7 @@ public class AutoInstallsLayout {
* Helper method to allow extending the parser capabilities
*/
protected long invalidPackageOrClass(XmlResourceParser parser) {
- if (LOGD) Log.d(TAG, "Skipping invalid <favorite> with no component");
+ Log.w(TAG, "Skipping invalid <favorite> with no component");
return -1;
}
}
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index c8de9df10..c11824054 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -34,10 +34,12 @@ public abstract class BaseContainerView extends LinearLayout implements Insettab
// The bounds of the search bar. Only the left, top, right are used to inset the
// search bar and the height is determined by the measurement of the layout
private Rect mFixedSearchBarBounds = new Rect();
- // The bounds of the container
+ // The computed bounds of the search bar
+ private Rect mSearchBarBounds = new Rect();
+ // The computed bounds of the container
protected Rect mContentBounds = new Rect();
- // The padding to apply to the container to achieve the bounds
- protected Rect mContentPadding = new Rect();
+ // The computed padding to apply to the container to achieve the container bounds
+ private Rect mContentPadding = new Rect();
// The inset to apply to the edges and between the search bar and the container
private int mContainerBoundsInset;
private boolean mHasSearchBar;
@@ -90,7 +92,7 @@ public abstract class BaseContainerView extends LinearLayout implements Insettab
*/
protected void updateBackgroundAndPaddings() {
Rect padding;
- Rect searchBarBounds = new Rect(mFixedSearchBarBounds);
+ Rect searchBarBounds = new Rect();
if (!isValidSearchBarBounds(mFixedSearchBarBounds)) {
// Use the default bounds
padding = new Rect(mInsets.left + mContainerBoundsInset,
@@ -110,14 +112,20 @@ public abstract class BaseContainerView extends LinearLayout implements Insettab
(mHasSearchBar ? 0 : (mInsets.top + mContainerBoundsInset)),
getMeasuredWidth() - mFixedSearchBarBounds.right,
mInsets.bottom + mContainerBoundsInset);
+
+ // Use the search bounds
+ searchBarBounds.set(mFixedSearchBarBounds);
}
- if (!padding.equals(mContentPadding) || !searchBarBounds.equals(mFixedSearchBarBounds)) {
+
+ // If either the computed container padding has changed, or the computed search bar bounds
+ // has changed, then notify the container
+ if (!padding.equals(mContentPadding) || !searchBarBounds.equals(mSearchBarBounds)) {
mContentPadding.set(padding);
mContentBounds.set(padding.left, padding.top,
getMeasuredWidth() - padding.right,
getMeasuredHeight() - padding.bottom);
- mFixedSearchBarBounds.set(searchBarBounds);
- onUpdateBackgroundAndPaddings(mFixedSearchBarBounds, padding);
+ mSearchBarBounds.set(searchBarBounds);
+ onUpdateBackgroundAndPaddings(mSearchBarBounds, padding);
}
}
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 0fae427e8..f0d8b3b3d 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -92,9 +92,15 @@ public abstract class BaseRecyclerView extends RecyclerView
// TODO(winsonc): If we want to animate the section heads while scrolling, we can
// initiate that here if the recycler view scroll state is not
// RecyclerView.SCROLL_STATE_IDLE.
+
+ onUpdateScrollbar(dy);
}
}
+ public void reset() {
+ mScrollbar.reattachThumbToScroll();
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -143,7 +149,7 @@ public abstract class BaseRecyclerView extends RecyclerView
mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
break;
}
- return mScrollbar.isDragging();
+ return mScrollbar.isDraggingThumb();
}
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
@@ -185,12 +191,10 @@ public abstract class BaseRecyclerView extends RecyclerView
* AvailableScrollHeight = Total height of the all items - last page height
*
* This assumes that all rows are the same height.
- *
- * @param yOffset the offset from the top of the recycler view to start tracking.
*/
- protected int getAvailableScrollHeight(int rowCount, int rowHeight, int yOffset) {
+ protected int getAvailableScrollHeight(int rowCount, int rowHeight) {
int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
- int scrollHeight = getPaddingTop() + yOffset + rowCount * rowHeight + getPaddingBottom();
+ int scrollHeight = getPaddingTop() + rowCount * rowHeight + getPaddingBottom();
int availableScrollHeight = scrollHeight - visibleHeight;
return availableScrollHeight;
}
@@ -222,7 +226,7 @@ public abstract class BaseRecyclerView extends RecyclerView
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
- onUpdateScrollbar();
+ onUpdateScrollbar(0);
mScrollbar.draw(canvas);
}
@@ -234,24 +238,21 @@ public abstract class BaseRecyclerView extends RecyclerView
* @param scrollPosState the current scroll position
* @param rowCount the number of rows, used to calculate the total scroll height (assumes that
* all rows are the same height)
- * @param yOffset the offset to start tracking in the recycler view (only used for all apps)
*/
protected void synchronizeScrollBarThumbOffsetToViewScroll(ScrollPositionState scrollPosState,
- int rowCount, int yOffset) {
- int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight,
- yOffset);
- int availableScrollBarHeight = getAvailableScrollBarHeight();
-
+ int rowCount) {
// Only show the scrollbar if there is height to be scrolled
+ int availableScrollBarHeight = getAvailableScrollBarHeight();
+ int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight);
if (availableScrollHeight <= 0) {
- mScrollbar.setScrollbarThumbOffset(-1, -1);
+ mScrollbar.setThumbOffset(-1, -1);
return;
}
// Calculate the current scroll position, the scrollY of the recycler view accounts for the
// view padding, while the scrollBarY is drawn right up to the background padding (ignoring
// padding)
- int scrollY = getPaddingTop() + yOffset +
+ int scrollY = getPaddingTop() +
(scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
int scrollBarY = mBackgroundPadding.top +
(int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
@@ -261,9 +262,9 @@ public abstract class BaseRecyclerView extends RecyclerView
if (Utilities.isRtl(getResources())) {
scrollBarX = mBackgroundPadding.left;
} else {
- scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getWidth();
+ scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
}
- mScrollbar.setScrollbarThumbOffset(scrollBarX, scrollBarY);
+ mScrollbar.setThumbOffset(scrollBarX, scrollBarY);
}
/**
@@ -276,10 +277,15 @@ public abstract class BaseRecyclerView extends RecyclerView
* Updates the bounds for the scrollbar.
* <p>Override in each subclass of this base class.
*/
- public abstract void onUpdateScrollbar();
+ public abstract void onUpdateScrollbar(int dy);
/**
* <p>Override in each subclass of this base class.
*/
public void onFastScrollCompleted() {}
+
+ /**
+ * Returns information about the item that the recycler view is currently scrolled to.
+ */
+ protected abstract void getCurScrollState(ScrollPositionState stateOut);
} \ No newline at end of file
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index 2c4184dc4..fcee7e8dd 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -23,6 +23,7 @@ import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.MotionEvent;
@@ -51,14 +52,21 @@ public class BaseRecyclerViewFastScrollBar {
private int mThumbActiveColor;
@Thunk Point mThumbOffset = new Point(-1, -1);
@Thunk Paint mThumbPaint;
- private Paint mTrackPaint;
private int mThumbMinWidth;
private int mThumbMaxWidth;
@Thunk int mThumbWidth;
@Thunk int mThumbHeight;
+ private int mThumbCurvature;
+ private Path mThumbPath = new Path();
+ private Paint mTrackPaint;
+ private int mTrackWidth;
+ private float mLastTouchY;
// The inset is the buffer around which a point will still register as a click on the scrollbar
private int mTouchInset;
private boolean mIsDragging;
+ private boolean mIsThumbDetached;
+ private boolean mCanThumbDetach;
+ private boolean mIgnoreDragGesture;
// This is the offset from the top of the scrollbar when the user first starts touching. To
// prevent jumping, this offset is applied as the user scrolls.
@@ -72,51 +80,74 @@ public class BaseRecyclerViewFastScrollBar {
mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
mTrackPaint = new Paint();
mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
- mTrackPaint.setAlpha(0);
+ mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
mThumbInactiveColor = rv.getFastScrollerThumbInactiveColor(
res.getColor(R.color.container_fastscroll_thumb_inactive_color));
mThumbActiveColor = res.getColor(R.color.container_fastscroll_thumb_active_color);
mThumbPaint = new Paint();
+ mThumbPaint.setAntiAlias(true);
mThumbPaint.setColor(mThumbInactiveColor);
+ mThumbPaint.setStyle(Paint.Style.FILL);
mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
+ mThumbCurvature = mThumbMaxWidth - mThumbMinWidth;
mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
}
- public void setScrollbarThumbOffset(int x, int y) {
+ public void setDetachThumbOnFastScroll() {
+ mCanThumbDetach = true;
+ }
+
+ public void reattachThumbToScroll() {
+ mIsThumbDetached = false;
+ }
+
+ public void setThumbOffset(int x, int y) {
if (mThumbOffset.x == x && mThumbOffset.y == y) {
return;
}
- mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+ mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+ mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
mThumbOffset.set(x, y);
- mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
- mRv.getHeight()));
+ updateThumbPath();
+ mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+ mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
mRv.invalidate(mInvalidateRect);
}
- // Setter/getter for the search bar width for animations
- public void setWidth(int width) {
- mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+ public Point getThumbOffset() {
+ return mThumbOffset;
+ }
+
+ // Setter/getter for the thumb bar width for animations
+ public void setThumbWidth(int width) {
+ mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+ mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
mThumbWidth = width;
- mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
- mRv.getHeight()));
+ updateThumbPath();
+ mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+ mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
mRv.invalidate(mInvalidateRect);
}
- public int getWidth() {
+ public int getThumbWidth() {
return mThumbWidth;
}
- // Setter/getter for the track background alpha for animations
- public void setTrackAlpha(int alpha) {
- mTrackPaint.setAlpha(alpha);
- mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+ // Setter/getter for the track bar width for animations
+ public void setTrackWidth(int width) {
+ mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
+ mRv.getHeight());
+ mTrackWidth = width;
+ updateThumbPath();
+ mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
+ mRv.getHeight());
mRv.invalidate(mInvalidateRect);
}
- public int getTrackAlpha() {
- return mTrackPaint.getAlpha();
+ public int getTrackWidth() {
+ return mTrackWidth;
}
public int getThumbHeight() {
@@ -127,10 +158,18 @@ public class BaseRecyclerViewFastScrollBar {
return mThumbMaxWidth;
}
- public boolean isDragging() {
+ public float getLastTouchY() {
+ return mLastTouchY;
+ }
+
+ public boolean isDraggingThumb() {
return mIsDragging;
}
+ public boolean isThumbDetached() {
+ return mIsThumbDetached;
+ }
+
/**
* Handles the touch event and determines whether to show the fast scroller (or updates it if
* it is already showing).
@@ -142,16 +181,21 @@ public class BaseRecyclerViewFastScrollBar {
int y = (int) ev.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
- if (isNearPoint(downX, downY)) {
+ if (isNearThumb(downX, downY)) {
mTouchOffset = downY - mThumbOffset.y;
}
break;
case MotionEvent.ACTION_MOVE:
- // Check if we should start scrolling
- if (!mIsDragging && isNearPoint(downX, downY) &&
+ // Check if we should start scrolling, but ignore this fastscroll gesture if we have
+ // exceeded some fixed movement
+ mIgnoreDragGesture |= Math.abs(y - downY) > config.getScaledPagingTouchSlop();
+ if (!mIsDragging && !mIgnoreDragGesture && isNearThumb(downX, lastY) &&
Math.abs(y - downY) > config.getScaledTouchSlop()) {
mRv.getParent().requestDisallowInterceptTouchEvent(true);
mIsDragging = true;
+ if (mCanThumbDetach) {
+ mIsThumbDetached = true;
+ }
mTouchOffset += (lastY - downY);
mPopup.animateVisibility(true);
animateScrollbar(true);
@@ -166,11 +210,14 @@ public class BaseRecyclerViewFastScrollBar {
mPopup.setSectionName(sectionName);
mPopup.animateVisibility(!sectionName.isEmpty());
mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
+ mLastTouchY = boundedY;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mTouchOffset = 0;
+ mLastTouchY = 0;
+ mIgnoreDragGesture = false;
if (mIsDragging) {
mIsDragging = false;
mPopup.animateVisibility(false);
@@ -189,8 +236,7 @@ public class BaseRecyclerViewFastScrollBar {
if (mTrackPaint.getAlpha() > 0) {
canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight(), mTrackPaint);
}
- canvas.drawRect(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
- mThumbOffset.y + mThumbHeight, mThumbPaint);
+ canvas.drawPath(mThumbPath, mThumbPaint);
// Draw the popup
mPopup.draw(canvas);
@@ -203,30 +249,49 @@ public class BaseRecyclerViewFastScrollBar {
if (mScrollbarAnimator != null) {
mScrollbarAnimator.cancel();
}
- ObjectAnimator trackAlphaAnim = ObjectAnimator.ofInt(this, "trackAlpha",
- isScrolling ? MAX_TRACK_ALPHA : 0);
- ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "width",
- isScrolling ? mThumbMaxWidth : mThumbMinWidth);
- ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
- mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
- colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animator) {
- mThumbPaint.setColor((Integer) animator.getAnimatedValue());
- mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
- mThumbOffset.y + mThumbHeight);
- }
- });
+
mScrollbarAnimator = new AnimatorSet();
- mScrollbarAnimator.playTogether(trackAlphaAnim, thumbWidthAnim, colorAnimation);
+ ObjectAnimator trackWidthAnim = ObjectAnimator.ofInt(this, "trackWidth",
+ isScrolling ? mThumbMaxWidth : mThumbMinWidth);
+ ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "thumbWidth",
+ isScrolling ? mThumbMaxWidth : mThumbMinWidth);
+ mScrollbarAnimator.playTogether(trackWidthAnim, thumbWidthAnim);
+ if (mThumbActiveColor != mThumbInactiveColor) {
+ ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
+ mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
+ colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ mThumbPaint.setColor((Integer) animator.getAnimatedValue());
+ mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
+ mThumbOffset.y + mThumbHeight);
+ }
+ });
+ mScrollbarAnimator.play(colorAnimation);
+ }
mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
mScrollbarAnimator.start();
}
/**
+ * Updates the path for the thumb drawable.
+ */
+ private void updateThumbPath() {
+ mThumbCurvature = mThumbMaxWidth - mThumbWidth;
+ mThumbPath.reset();
+ mThumbPath.moveTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y); // tr
+ mThumbPath.lineTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight); // br
+ mThumbPath.lineTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight); // bl
+ mThumbPath.cubicTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight,
+ mThumbOffset.x - mThumbCurvature, mThumbOffset.y + mThumbHeight / 2,
+ mThumbOffset.x, mThumbOffset.y); // bl2tl
+ mThumbPath.close();
+ }
+
+ /**
* Returns whether the specified points are near the scroll bar bounds.
*/
- private boolean isNearPoint(int x, int y) {
+ private boolean isNearThumb(int x, int y) {
mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
mThumbOffset.y + mThumbHeight);
mTmpRect.inset(mTouchInset, mTouchInset);
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index a0be8ea2b..507087824 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -34,11 +34,13 @@ import android.util.SparseArray;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.TextView;
+
import com.android.launcher3.IconCache.IconLoadRequest;
import com.android.launcher3.model.PackageItemInfo;
@@ -508,7 +510,7 @@ public class BubbleTextView extends TextView
mIcon.setBounds(0, 0, iconSize, iconSize);
}
if (mLayoutHorizontal) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
setCompoundDrawablesRelative(mIcon, null, null, null);
} else {
setCompoundDrawables(mIcon, null, null, null);
@@ -538,6 +540,13 @@ public class BubbleTextView extends TextView
} else if (info instanceof ShortcutInfo) {
applyFromShortcutInfo((ShortcutInfo) info,
LauncherAppState.getInstance().getIconCache());
+ if ((info.rank < FolderIcon.NUM_ITEMS_IN_PREVIEW) && (info.container >= 0)) {
+ View folderIcon =
+ mLauncher.getWorkspace().getHomescreenIconByItemId(info.container);
+ if (folderIcon != null) {
+ folderIcon.invalidate();
+ }
+ }
} else if (info instanceof PackageItemInfo) {
applyFromPackageItemInfo((PackageItemInfo) info);
}
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index b7f89d02a..2baa6d8ee 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -24,7 +24,6 @@ import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
-import android.content.res.Configuration;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.PointF;
@@ -93,7 +92,7 @@ public abstract class ButtonDropTarget extends TextView
// drawableLeft and drawableStart.
mDrawable = getResources().getDrawable(resId);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
} else {
setCompoundDrawablesWithIntrinsicBounds(mDrawable, null, null, null);
@@ -114,7 +113,7 @@ public abstract class ButtonDropTarget extends TextView
@Override
public final void onDragEnter(DragObject d) {
d.dragView.setColor(mHoverColor);
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
animateTextColor(mHoverColor);
} else {
if (mCurrentFilter == null) {
@@ -131,8 +130,8 @@ public abstract class ButtonDropTarget extends TextView
// Do nothing
}
- protected void resetHoverColor() {
- if (Utilities.isLmpOrAbove()) {
+ protected void resetHoverColor() {
+ if (Utilities.ATLEAST_LOLLIPOP) {
animateTextColor(mOriginalTextColor.getDefaultColor());
} else {
mDrawable.setColorFilter(null);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 809688712..84e2d49c2 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -54,7 +54,6 @@ import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.accessibility.FolderAccessibilityHelper;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.util.Thunk;
-import com.android.launcher3.widget.PendingAddWidgetInfo;
import java.util.ArrayList;
import java.util.Arrays;
@@ -211,6 +210,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
mBackground.setCallback(this);
+ mBackground.setAlpha((int) (mBackgroundAlpha * 255));
mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
grid.iconSizePx);
@@ -414,7 +414,11 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
if (mIsDragOverlapping) {
mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
} else {
- mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
+ if (mBackgroundAlpha > 0f) {
+ mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
+ } else {
+ mBackground.resetTransition();
+ }
}
invalidate();
}
@@ -2681,65 +2685,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
resultRect.set(x, y, x + width, y + height);
}
- /**
- * 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 static int[] rectToCell(Launcher launcher, int width, int height, int[] result) {
- return rectToCell(launcher.getDeviceProfile(), launcher, width, height, result);
- }
-
- public static int[] rectToCell(DeviceProfile grid, Context context, int width, int height,
- int[] result) {
- Rect padding = grid.getWorkspacePadding(Utilities.isRtl(context.getResources()));
-
- // Always assume we're working with the smallest span to make sure we
- // reserve enough space in both orientations.
- int parentWidth = DeviceProfile.calculateCellWidth(grid.widthPx
- - padding.left - padding.right, (int) grid.inv.numColumns);
- int parentHeight = DeviceProfile.calculateCellHeight(grid.heightPx
- - padding.top - padding.bottom, (int) grid.inv.numRows);
- int smallerSize = Math.min(parentWidth, parentHeight);
-
- // Always round up to next largest cell
- int spanX = (int) Math.ceil(width / (float) smallerSize);
- int spanY = (int) Math.ceil(height / (float) smallerSize);
-
- if (result == null) {
- return new int[] { spanX, spanY };
- }
- result[0] = spanX;
- result[1] = spanY;
- return result;
- }
-
- /**
- * Calculate the grid spans needed to fit given item
- */
- public void calculateSpans(ItemInfo info) {
- final int minWidth;
- final int minHeight;
-
- if (info instanceof LauncherAppWidgetInfo) {
- minWidth = ((LauncherAppWidgetInfo) info).minWidth;
- minHeight = ((LauncherAppWidgetInfo) info).minHeight;
- } else if (info instanceof PendingAddWidgetInfo) {
- minWidth = ((PendingAddWidgetInfo) info).minWidth;
- minHeight = ((PendingAddWidgetInfo) info).minHeight;
- } else {
- // It's not a widget, so it must be 1x1
- info.spanX = info.spanY = 1;
- return;
- }
- int[] spans = rectToCell(mLauncher, minWidth, minHeight, null);
- info.spanX = spans[0];
- info.spanY = spans[1];
- }
-
private void clearOccupiedCells() {
for (int x = 0; x < mCountX; x++) {
for (int y = 0; y < mCountY; y++) {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 62b05b0d6..774594fe2 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -56,7 +56,6 @@ public class DeviceProfile {
private final int overviewModeBarItemWidthPx;
private final int overviewModeBarSpacerWidthPx;
private final float overviewModeIconZoneRatio;
- private final float overviewModeScaleFactor;
// Workspace
private int desiredWorkspaceLeftRightMarginPx;
@@ -136,8 +135,6 @@ public class DeviceProfile {
res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
overviewModeIconZoneRatio =
res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
- overviewModeScaleFactor =
- res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
iconDrawablePaddingOriginalPx =
res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
@@ -334,18 +331,11 @@ public class DeviceProfile {
}
}
- Rect getOverviewModeButtonBarRect() {
+ int getOverviewModeButtonBarHeight() {
int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
- return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
- }
-
- public float getOverviewModeScale(boolean isLayoutRtl) {
- Rect workspacePadding = getWorkspacePadding(isLayoutRtl);
- Rect overviewBar = getOverviewModeButtonBarRect();
- int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
- return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
+ return zoneHeight;
}
// The rect returned will be extended to below the system ui that covers the workspace
@@ -394,7 +384,7 @@ public class DeviceProfile {
final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
// Layout the search bar space
- View searchBar = launcher.getSearchBar();
+ View searchBar = launcher.getSearchDropTargetBar();
lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
if (hasVerticalBarLayout) {
// Vertical search bar space -- The search bar is fixed in the layout to be on the left
@@ -476,7 +466,7 @@ public class DeviceProfile {
// Layout the Overview Mode
ViewGroup overviewMode = launcher.getOverviewPanel();
if (overviewMode != null) {
- Rect r = getOverviewModeButtonBarRect();
+ int overviewButtonBarHeight = getOverviewModeButtonBarHeight();
lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
@@ -485,7 +475,7 @@ public class DeviceProfile {
int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
lp.width = Math.min(availableWidthPx, maxWidth);
- lp.height = r.height();
+ lp.height = overviewButtonBarHeight;
overviewMode.setLayoutParams(lp);
if (lp.width > totalItemWidth && visibleChildCount > 1) {
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index aaa14e6a6..1c18747c1 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -159,7 +159,7 @@ public class DragLayer extends InsettableFrameLayout {
}
private boolean isEventOverDropTargetBar(MotionEvent ev) {
- getDescendantRectRelativeToSelf(mLauncher.getSearchBar(), mHitRect);
+ getDescendantRectRelativeToSelf(mLauncher.getSearchDropTargetBar(), mHitRect);
if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
return true;
}
@@ -321,7 +321,7 @@ public class DragLayer extends InsettableFrameLayout {
childrenForAccessibility.add(currentFolder);
if (isInAccessibleDrag()) {
- childrenForAccessibility.add(mLauncher.getSearchBar());
+ childrenForAccessibility.add(mLauncher.getSearchDropTargetBar());
}
} else {
super.addChildrenForAccessibility(childrenForAccessibility);
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
index dfa8202a7..2acfc6140 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/DragView.java
@@ -131,7 +131,7 @@ public class DragView extends View {
measure(ms, ms);
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
setElevation(getResources().getDimension(R.dimen.drag_elevation));
}
}
@@ -252,14 +252,14 @@ public class DragView extends View {
setColorScale(color, m2);
m1.postConcat(m2);
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
animateFilterTo(m1.getArray());
} else {
mPaint.setColorFilter(new ColorMatrixColorFilter(m1));
invalidate();
}
} else {
- if (!Utilities.isLmpOrAbove() || mCurrentFilter == null) {
+ if (!Utilities.ATLEAST_LOLLIPOP || mCurrentFilter == null) {
mPaint.setColorFilter(null);
invalidate();
} else {
diff --git a/src/com/android/launcher3/allapps/AllAppsSearchEditView.java b/src/com/android/launcher3/ExtendedEditText.java
index b7dcd66ed..c7b64ec7d 100644
--- a/src/com/android/launcher3/allapps/AllAppsSearchEditView.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.allapps;
+package com.android.launcher3;
import android.content.Context;
import android.util.AttributeSet;
@@ -22,28 +22,28 @@ import android.widget.EditText;
/**
- * The edit text for the search container
+ * The edit text that reports back when the back key has been pressed.
*/
-public class AllAppsSearchEditView extends EditText {
+public class ExtendedEditText extends EditText {
/**
* Implemented by listeners of the back key.
*/
public interface OnBackKeyListener {
- public void onBackKey();
+ public boolean onBackKey();
}
private OnBackKeyListener mBackKeyListener;
- public AllAppsSearchEditView(Context context) {
- this(context, null);
+ public ExtendedEditText(Context context) {
+ super(context);
}
- public AllAppsSearchEditView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
+ public ExtendedEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
}
- public AllAppsSearchEditView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public ExtendedEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@@ -56,7 +56,7 @@ public class AllAppsSearchEditView extends EditText {
// If this is a back key, propagate the key back to the listener
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
if (mBackKeyListener != null) {
- mBackKeyListener.onBackKey();
+ return mBackKeyListener.onBackKey();
}
return false;
}
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 2e19f6eba..c1aa35669 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -124,7 +124,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
@Thunk FolderPagedView mContent;
@Thunk View mContentWrapper;
- FolderEditText mFolderName;
+ ExtendedEditText mFolderName;
private View mFooter;
private int mFooterHeight;
@@ -196,8 +196,15 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
mContent = (FolderPagedView) findViewById(R.id.folder_content);
mContent.setFolder(this);
- mFolderName = (FolderEditText) findViewById(R.id.folder_name);
- mFolderName.setFolder(this);
+ mFolderName = (ExtendedEditText) findViewById(R.id.folder_name);
+ mFolderName.setOnBackKeyListener(new ExtendedEditText.OnBackKeyListener() {
+ @Override
+ public boolean onBackKey() {
+ // Close the activity on back key press
+ doneEditingFolderName(true);
+ return false;
+ }
+ });
mFolderName.setOnFocusChangeListener(this);
// We disable action mode for now since it messes up the view on phones
@@ -275,7 +282,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
@Override
public void enableAccessibleDrag(boolean enable) {
- mLauncher.getSearchBar().enableAccessibleDrag(enable);
+ mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable);
for (int i = 0; i < mContent.getChildCount(); i++) {
mContent.getPageAt(i).enableAccessibleDrag(enable, CellLayout.FOLDER_ACCESSIBILITY_DRAG);
}
@@ -446,7 +453,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
Animator openFolderAnim = null;
final Runnable onCompleteRunnable;
- if (!Utilities.isLmpOrAbove()) {
+ if (!Utilities.ATLEAST_LOLLIPOP) {
positionAndSizeAsIcon();
centerAboutIcon();
@@ -561,7 +568,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
public void onAnimationEnd(Animator animation) {
mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION)
.translationX(0)
- .setInterpolator(Utilities.isLmpOrAbove() ?
+ .setInterpolator(Utilities.ATLEAST_LOLLIPOP ?
AnimationUtils.loadInterpolator(mLauncher,
android.R.interpolator.fast_out_slow_in)
: new LogDecelerateInterpolator(100, 0));
@@ -1389,7 +1396,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
}
// Compares item position based on rank and position giving priority to the rank.
- private static final Comparator<ItemInfo> ITEM_POS_COMPARATOR = new Comparator<ItemInfo>() {
+ public static final Comparator<ItemInfo> ITEM_POS_COMPARATOR = new Comparator<ItemInfo>() {
@Override
public int compare(ItemInfo lhs, ItemInfo rhs) {
diff --git a/src/com/android/launcher3/FolderEditText.java b/src/com/android/launcher3/FolderEditText.java
deleted file mode 100644
index c31100899..000000000
--- a/src/com/android/launcher3/FolderEditText.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.android.launcher3;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.widget.EditText;
-
-public class FolderEditText extends EditText {
-
- private Folder mFolder;
-
- public FolderEditText(Context context) {
- super(context);
- }
-
- public FolderEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public FolderEditText(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public void setFolder(Folder folder) {
- mFolder = folder;
- }
-
- @Override
- public boolean onKeyPreIme(int keyCode, KeyEvent event) {
- // Catch the back button on the soft keyboard so that we can just close the activity
- if (event.getKeyCode() == android.view.KeyEvent.KEYCODE_BACK) {
- mFolder.doneEditingFolderName(true);
- }
- return super.onKeyPreIme(keyCode, event);
- }
-}
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index f2ec1b68c..cc9c5738a 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -329,6 +329,10 @@ public class FolderPagedView extends PagedView {
lp.cellY = info.cellY;
currentPage.addViewToCellLayout(
v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
+
+ if (rank < FolderIcon.NUM_ITEMS_IN_PREVIEW && v instanceof BubbleTextView) {
+ ((BubbleTextView) v).verifyHighRes();
+ }
}
rank ++;
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 916418f18..59ab8397d 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -540,7 +540,7 @@ public class IconCache {
mCache.put(cacheKey, entry);
// Check the DB first.
- if (!getEntryFromDB(componentName, user, entry, useLowResIcon)) {
+ if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
if (info != null) {
entry.icon = Utilities.createIconBitmap(info.getBadgedIcon(mIconDpi), mContext);
} else {
@@ -579,7 +579,14 @@ public class IconCache {
Bitmap icon, CharSequence title) {
removeFromMemCacheLocked(packageName, user);
- CacheEntry entry = getEntryForPackageLocked(packageName, user, false);
+ ComponentKey cacheKey = getPackageKey(packageName, user);
+ CacheEntry entry = mCache.get(cacheKey);
+
+ // For icon caching, do not go through DB. Just update the in-memory entry.
+ if (entry == null) {
+ entry = new CacheEntry();
+ mCache.put(cacheKey, entry);
+ }
if (!TextUtils.isEmpty(title)) {
entry.title = title;
}
@@ -588,15 +595,18 @@ public class IconCache {
}
}
+ private static ComponentKey getPackageKey(String packageName, UserHandleCompat user) {
+ ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
+ return new ComponentKey(cn, user);
+ }
+
/**
* Gets an entry for the package, which can be used as a fallback entry for various components.
* This method is not thread safe, it must be called from a synchronized method.
- *
*/
private CacheEntry getEntryForPackageLocked(String packageName, UserHandleCompat user,
boolean useLowResIcon) {
- ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
- ComponentKey cacheKey = new ComponentKey(cn, user);
+ ComponentKey cacheKey = getPackageKey(packageName, user);
CacheEntry entry = mCache.get(cacheKey);
if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
@@ -604,9 +614,11 @@ public class IconCache {
boolean entryUpdated = true;
// Check the DB first.
- if (!getEntryFromDB(cn, user, entry, useLowResIcon)) {
+ if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
try {
- PackageInfo info = mPackageManager.getPackageInfo(packageName, 0);
+ int flags = UserHandleCompat.myUserHandle().equals(user) ? 0 :
+ PackageManager.GET_UNINSTALLED_PACKAGES;
+ PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
ApplicationInfo appInfo = info.applicationInfo;
if (appInfo == null) {
throw new NameNotFoundException("ApplicationInfo is null");
@@ -622,7 +634,8 @@ public class IconCache {
// package updates.
ContentValues values =
newContentValues(entry.icon, entry.title.toString(), mPackageBgColor);
- addIconToDB(values, cn, info, mUserManager.getSerialNumberForUser(user));
+ addIconToDB(values, cacheKey.componentName, info,
+ mUserManager.getSerialNumberForUser(user));
} catch (NameNotFoundException e) {
if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
@@ -649,7 +662,7 @@ public class IconCache {
* @param dpi the native density of the icon
*/
public void preloadIcon(ComponentName componentName, Bitmap icon, int dpi, String label,
- long userSerial) {
+ long userSerial, InvariantDeviceProfile idp) {
// TODO rescale to the correct native DPI
try {
PackageManager packageManager = mContext.getPackageManager();
@@ -660,21 +673,22 @@ public class IconCache {
// pass
}
- ContentValues values = newContentValues(icon, label, Color.TRANSPARENT);
+ ContentValues values = newContentValues(
+ Bitmap.createScaledBitmap(icon, idp.iconBitmapSize, idp.iconBitmapSize, true),
+ label, Color.TRANSPARENT);
values.put(IconDB.COLUMN_COMPONENT, componentName.flattenToString());
values.put(IconDB.COLUMN_USER, userSerial);
mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
SQLiteDatabase.CONFLICT_REPLACE);
}
- private boolean getEntryFromDB(ComponentName component, UserHandleCompat user,
- CacheEntry entry, boolean lowRes) {
+ private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
Cursor c = mIconDb.getReadableDatabase().query(IconDB.TABLE_NAME,
new String[] {lowRes ? IconDB.COLUMN_ICON_LOW_RES : IconDB.COLUMN_ICON,
IconDB.COLUMN_LABEL},
IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
- new String[] {component.flattenToString(),
- Long.toString(mUserManager.getSerialNumberForUser(user))},
+ new String[] {cacheKey.componentName.flattenToString(),
+ Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))},
null, null, null);
try {
if (c.moveToNext()) {
@@ -685,7 +699,8 @@ public class IconCache {
entry.title = "";
entry.contentDescription = "";
} else {
- entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(
+ entry.title, cacheKey.user);
}
return true;
}
@@ -774,7 +789,7 @@ public class IconCache {
}
private static final class IconDB extends SQLiteOpenHelper {
- private final static int DB_VERSION = 6;
+ private final static int DB_VERSION = 7;
private final static String TABLE_NAME = "icons";
private final static String COLUMN_ROWID = "rowid";
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 6648b6e74..1f843cb70 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -116,8 +116,6 @@ import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
@@ -132,8 +130,7 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
public class Launcher extends Activity
implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
- View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener,
- LauncherStateTransitionAnimation.Callbacks {
+ View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {
static final String TAG = "Launcher";
static final boolean LOGD = false;
@@ -304,6 +301,8 @@ public class Launcher extends Activity
private boolean mHasFocus = false;
private boolean mAttached = false;
+ private LauncherClings mClings;
+
private static LongArrayMap<FolderInfo> sFolders = new LongArrayMap<>();
private View.OnTouchListener mHapticFeedbackTouchListener;
@@ -361,18 +360,6 @@ public class Launcher extends Activity
}
}
- // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
- private static Method sClipRevealMethod = null;
- static {
- Class<?> activityOptionsClass = ActivityOptions.class;
- try {
- sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation",
- View.class, int.class, int.class, int.class, int.class);
- } catch (Exception e) {
- // Earlier version
- }
- }
-
@Thunk Runnable mBuildLayersRunnable = new Runnable() {
public void run() {
if (mWorkspace != null) {
@@ -452,7 +439,7 @@ public class Launcher extends Activity
mDragController = new DragController(this);
mInflater = getLayoutInflater();
- mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
+ mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);
mStats = new Stats(this);
@@ -663,12 +650,12 @@ public class Launcher extends Activity
public boolean isDraggingEnabled() {
// We prevent dragging when we are loading the workspace as it is possible to pick up a view
// that is subsequently removed from the workspace in startBinding().
- return !mModel.isLoadingWorkspace();
+ return !isWorkspaceLoading();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static int generateViewId() {
- if (Build.VERSION.SDK_INT >= 17) {
+ if (Utilities.ATLEAST_JB_MR1) {
return View.generateViewId();
} else {
// View.generateViewId() is not available. The following fallback logic is a copy
@@ -743,6 +730,7 @@ public class Launcher extends Activity
};
if (requestCode == REQUEST_BIND_APPWIDGET) {
+ // This is called only if the user did not previously have permissions to bind widgets
final int appWidgetId = data != null ?
data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
if (resultCode == RESULT_CANCELED) {
@@ -752,6 +740,10 @@ public class Launcher extends Activity
} else if (resultCode == RESULT_OK) {
addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
+
+ // When the user has granted permission to bind widgets, we should check to see if
+ // we can inflate the default search bar widget.
+ getOrCreateQsbBar();
}
return;
} else if (requestCode == REQUEST_PICK_WALLPAPER) {
@@ -1004,6 +996,12 @@ public class Launcher extends Activity
mPaused = false;
if (mRestoring || mOnResumeNeedsLoad) {
setWorkspaceLoading(true);
+
+ // If we're starting binding all over again, clear any bind calls we'd postponed in
+ // the past (see waitUntilResume) -- we don't need them since we're starting binding
+ // from scratch again
+ mBindOnResumeCallbacks.clear();
+
mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
mRestoring = false;
mOnResumeNeedsLoad = false;
@@ -1553,23 +1551,6 @@ public class Launcher extends Activity
}
}
- private int[] getSpanForWidget(ComponentName component, int minWidth, int minHeight) {
- Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(this, component, null);
- // We want to account for the extra amount of padding that we are adding to the widget
- // to ensure that it gets the full amount of space that it has requested
- int requiredWidth = minWidth + padding.left + padding.right;
- int requiredHeight = minHeight + padding.top + padding.bottom;
- return CellLayout.rectToCell(this, requiredWidth, requiredHeight, null);
- }
-
- public int[] getSpanForWidget(AppWidgetProviderInfo info) {
- return getSpanForWidget(info.provider, info.minWidth, info.minHeight);
- }
-
- public int[] getMinSpanForWidget(AppWidgetProviderInfo info) {
- return getSpanForWidget(info.provider, info.minResizeWidth, info.minResizeHeight);
- }
-
/**
* Add a widget to the workspace.
*
@@ -1666,18 +1647,18 @@ public class Launcher extends Activity
}
registerReceiver(mReceiver, filter);
FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
- setupTransparentSystemBarsForLmp();
+ setupTransparentSystemBarsForLollipop();
mAttached = true;
mVisible = true;
}
/**
- * Sets up transparent navigation and status bars in LMP.
+ * Sets up transparent navigation and status bars in Lollipop.
* This method is a no-op for other platform versions.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
- private void setupTransparentSystemBarsForLmp() {
- if (Utilities.isLmpOrAbove()) {
+ private void setupTransparentSystemBarsForLollipop() {
+ if (Utilities.ATLEAST_LOLLIPOP) {
Window window = getWindow();
window.getAttributes().systemUiVisibility |=
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
@@ -1844,7 +1825,7 @@ public class Launcher extends Activity
return mOverviewPanel;
}
- public SearchDropTargetBar getSearchBar() {
+ public SearchDropTargetBar getSearchDropTargetBar() {
return mSearchDropTargetBar;
}
@@ -1880,29 +1861,22 @@ public class Launcher extends Activity
super.onNewIntent(intent);
// Close the menu
- if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+ Folder openFolder = mWorkspace.getOpenFolder();
+ boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
+ Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
+ != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+ boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
+ if (isActionMain) {
// also will cancel mWaitingForResult.
closeSystemDialogs();
- final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
- Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
- != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
-
if (mWorkspace == null) {
// Can be cases where mWorkspace is null, this prevents a NPE
return;
}
- Folder openFolder = mWorkspace.getOpenFolder();
// In all these cases, only animate if we're already on home
mWorkspace.exitWidgetResizeMode();
- boolean moveToDefaultScreen = mLauncherCallbacks != null ?
- mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
- if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
- openFolder == null && moveToDefaultScreen) {
- mWorkspace.moveToDefaultScreen(true);
- }
-
closeFolder();
exitSpringLoadedDragMode();
@@ -1936,13 +1910,30 @@ public class Launcher extends Activity
}
}
- if (DEBUG_RESUME_TIME) {
- Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
- }
-
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onNewIntent(intent);
}
+
+ // Defer moving to the default screen until after we callback to the LauncherCallbacks
+ // as slow logic in the callbacks eat into the time the scroller expects for the snapToPage
+ // animation.
+ if (isActionMain) {
+ boolean moveToDefaultScreen = mLauncherCallbacks != null ?
+ mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
+ if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
+ openFolder == null && moveToDefaultScreen) {
+ mWorkspace.post(new Runnable() {
+ @Override
+ public void run() {
+ mWorkspace.moveToDefaultScreen(true);
+ }
+ });
+ }
+ }
+
+ if (DEBUG_RESUME_TIME) {
+ Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
+ }
}
@Override
@@ -2150,6 +2141,15 @@ public class Launcher extends Activity
}
}
+ public void startSearchFromAllApps(View v, Intent searchIntent, String searchQuery) {
+ if (mLauncherCallbacks != null && mLauncherCallbacks.startSearchFromAllApps(searchQuery)) {
+ return;
+ }
+
+ // If not handled, then just start the provided search intent
+ startActivitySafely(v, searchIntent, null);
+ }
+
public boolean isOnCustomContent() {
return mWorkspace.isOnOrMovingToCustomContent();
}
@@ -2218,7 +2218,7 @@ public class Launcher extends Activity
mPendingAddInfo.screenId = -1;
mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
- mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
+ mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = 1;
mPendingAddInfo.dropPos = null;
}
@@ -2551,6 +2551,10 @@ public class Launcher extends Activity
if (!isAppsViewVisible()) {
showAppsView(true /* animated */, false /* resetListToTop */,
true /* updatePredictedApps */, false /* focusSearchBar */);
+
+ if (mLauncherCallbacks != null) {
+ mLauncherCallbacks.onClickAllAppsButton(v);
+ }
}
}
@@ -2679,6 +2683,7 @@ public class Launcher extends Activity
throw new IllegalArgumentException("Input must be a FolderIcon");
}
+ // TODO(sunnygoyal): Re-evaluate this code.
FolderIcon folderIcon = (FolderIcon) v;
final FolderInfo info = folderIcon.getFolderInfo();
Folder openFolder = mWorkspace.getFolderForTag(info);
@@ -2882,8 +2887,7 @@ public class Launcher extends Activity
Bundle optsBundle = null;
if (useLaunchAnimation) {
ActivityOptions opts = null;
- if (sClipRevealMethod != null) {
- // TODO: call method directly when Launcher3 can depend on M APIs
+ if (Utilities.ATLEAST_MARSHMALLOW) {
int left = 0, top = 0;
int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
if (v instanceof TextView) {
@@ -2897,22 +2901,12 @@ public class Launcher extends Activity
height = bounds.height();
}
}
- try {
- opts = (ActivityOptions) sClipRevealMethod.invoke(null, v,
- left, top, width, height);
- } catch (IllegalAccessException e) {
- Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
- sClipRevealMethod = null;
- } catch (InvocationTargetException e) {
- Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
- sClipRevealMethod = null;
- }
- }
- if (opts == null && !Utilities.isLmpOrAbove()) {
+ opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
+ } else if (!Utilities.ATLEAST_LOLLIPOP) {
// Below L, we use a scale up animation
opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
v.getMeasuredWidth(), v.getMeasuredHeight());
- } else if (opts == null && Utilities.isLmpMR1()) {
+ } else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
// On L devices, we use the device default slide-up transition.
// On L MR1 devices, we a custom version of the slide-up transition which
// doesn't have the delay present in the device default.
@@ -2941,7 +2935,7 @@ public class Launcher extends Activity
return false;
}
- @Thunk boolean startActivitySafely(View v, Intent intent, Object tag) {
+ public boolean startActivitySafely(View v, Intent intent, Object tag) {
boolean success = false;
if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
@@ -3027,7 +3021,7 @@ public class Launcher extends Activity
ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
scaleX, scaleY);
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
}
oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
@@ -3265,14 +3259,6 @@ public class Launcher extends Activity
}
}
- @Override
- public void onStateTransitionHideSearchBar() {
- // Hide the search bar
- if (mSearchDropTargetBar != null) {
- mSearchDropTargetBar.hideSearchBar(false /* animated */);
- }
- }
-
public void showWorkspace(boolean animated) {
showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null);
}
@@ -3290,16 +3276,9 @@ public class Launcher extends Activity
boolean changed = mState != State.WORKSPACE ||
mWorkspace.getState() != Workspace.State.NORMAL;
if (changed) {
- boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
mWorkspace.setVisibility(View.VISIBLE);
- mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL,
- snapToPage, animated, onCompleteRunnable);
-
- // Show the search bar (only animate if we were showing the drop target bar in spring
- // loaded mode)
- if (mSearchDropTargetBar != null) {
- mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
- }
+ mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
+ Workspace.State.NORMAL, snapToPage, animated, onCompleteRunnable);
// Set focus to the AppsCustomize button
if (mAllAppsButton != null) {
@@ -3323,7 +3302,8 @@ public class Launcher extends Activity
void showOverviewMode(boolean animated) {
mWorkspace.setVisibility(View.VISIBLE);
- mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW,
+ mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
+ Workspace.State.OVERVIEW,
WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
null /* onCompleteRunnable */);
mState = State.WORKSPACE;
@@ -3378,9 +3358,10 @@ public class Launcher extends Activity
}
if (toState == State.APPS) {
- mStateTransitionAnimation.startAnimationToAllApps(animated, focusSearchBar);
+ mStateTransitionAnimation.startAnimationToAllApps(mWorkspace.getState(), animated,
+ focusSearchBar);
} else {
- mStateTransitionAnimation.startAnimationToWidgets(animated);
+ mStateTransitionAnimation.startAnimationToWidgets(mWorkspace.getState(), animated);
}
// Change the state *after* we've called all the transition code
@@ -3402,10 +3383,9 @@ public class Launcher extends Activity
* new state.
*/
public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, int toPage,
- boolean animated, boolean hasOverlaySearchBar, HashMap<View, Integer> layerViews) {
+ boolean animated, HashMap<View, Integer> layerViews) {
Workspace.State fromState = mWorkspace.getState();
- Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated,
- hasOverlaySearchBar, layerViews);
+ Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated, layerViews);
updateInteraction(fromState, toState);
return anim;
}
@@ -3417,7 +3397,8 @@ public class Launcher extends Activity
return;
}
- mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED,
+ mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
+ Workspace.State.SPRING_LOADED,
WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true /* animated */,
null /* onCompleteRunnable */);
mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
@@ -3526,6 +3507,7 @@ public class Launcher extends Activity
mAppWidgetHost.setQsbWidgetId(widgetId);
if (widgetId != -1) {
mQsb = mAppWidgetHost.createView(this, widgetId, searchProvider);
+ mQsb.setId(R.id.qsb_widget);
mQsb.updateAppWidgetOptions(opts);
mQsb.setPadding(0, 0, 0, 0);
mSearchDropTargetBar.addView(mQsb);
@@ -3782,11 +3764,12 @@ public class Launcher extends Activity
continue;
}
+ final View view;
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
ShortcutInfo info = (ShortcutInfo) item;
- View shortcut = createShortcut(info);
+ view = createShortcut(info);
/*
* TODO: FIX collision case
@@ -3805,28 +3788,26 @@ public class Launcher extends Activity
}
}
}
-
- workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
- item.cellY, 1, 1);
- if (animateIcons) {
- // Animate all the applications up now
- shortcut.setAlpha(0f);
- shortcut.setScaleX(0f);
- shortcut.setScaleY(0f);
- bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
- newShortcutsScreenId = item.screenId;
- }
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
+ view = FolderIcon.fromXml(R.layout.folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
(FolderInfo) item, mIconCache);
- workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
- item.cellY, 1, 1);
break;
default:
throw new RuntimeException("Invalid Item Type");
}
+
+ workspace.addInScreenFromBind(view, item.container, item.screenId, item.cellX,
+ item.cellY, 1, 1);
+ if (animateIcons) {
+ // Animate all the applications up now
+ view.setAlpha(0f);
+ view.setScaleX(0f);
+ view.setScaleY(0f);
+ bounceAnims.add(createNewAppBounceAnimation(view, i));
+ newShortcutsScreenId = item.screenId;
+ }
}
if (animateIcons) {
@@ -3901,7 +3882,8 @@ public class Launcher extends Activity
if (!mIsSafeModeEnabled
&& ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
- && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
+ && (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
+
if (appWidgetInfo == null) {
if (DEBUG_WIDGETS) {
Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
@@ -3911,42 +3893,51 @@ public class Launcher extends Activity
LauncherModel.deleteItemFromDatabase(this, item);
return;
}
- // Note: This assumes that the id remap broadcast is received before this step.
- // If that is not the case, the id remap will be ignored and user may see the
- // click to setup view.
- PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo, null);
- pendingInfo.spanX = item.spanX;
- pendingInfo.spanY = item.spanY;
- pendingInfo.minSpanX = item.minSpanX;
- pendingInfo.minSpanY = item.minSpanY;
- Bundle options = null;
- WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
-
- int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
- boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
- newWidgetId, appWidgetInfo, options);
- // TODO consider showing a permission dialog when the widget is clicked.
- if (!success) {
- mAppWidgetHost.deleteAppWidgetId(newWidgetId);
- if (DEBUG_WIDGETS) {
- Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
- + " belongs to component " + item.providerName
- + ", as the launcher is unable to bing a new widget id");
+ // If we do not have a valid id, try to bind an id.
+ if ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0) {
+ // Note: This assumes that the id remap broadcast is received before this step.
+ // If that is not the case, the id remap will be ignored and user may see the
+ // click to setup view.
+ PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo, null);
+ pendingInfo.spanX = item.spanX;
+ pendingInfo.spanY = item.spanY;
+ pendingInfo.minSpanX = item.minSpanX;
+ pendingInfo.minSpanY = item.minSpanY;
+ Bundle options = null;
+ WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
+
+ int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
+ boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
+ newWidgetId, appWidgetInfo, options);
+
+ // TODO consider showing a permission dialog when the widget is clicked.
+ if (!success) {
+ mAppWidgetHost.deleteAppWidgetId(newWidgetId);
+ if (DEBUG_WIDGETS) {
+ Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
+ + " belongs to component " + item.providerName
+ + ", as the launcher is unable to bing a new widget id");
+ }
+ LauncherModel.deleteItemFromDatabase(this, item);
+ return;
}
- LauncherModel.deleteItemFromDatabase(this, item);
- return;
- }
- item.appWidgetId = newWidgetId;
+ item.appWidgetId = newWidgetId;
- // If the widget has a configure activity, it is still needs to set it up, otherwise
- // the widget is ready to go.
- item.restoreStatus = (appWidgetInfo.configure == null)
- ? LauncherAppWidgetInfo.RESTORE_COMPLETED
- : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+ // If the widget has a configure activity, it is still needs to set it up, otherwise
+ // the widget is ready to go.
+ item.restoreStatus = (appWidgetInfo.configure == null)
+ ? LauncherAppWidgetInfo.RESTORE_COMPLETED
+ : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
- LauncherModel.updateItemInDatabase(this, item);
+ LauncherModel.updateItemInDatabase(this, item);
+ } else if (((item.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0)
+ && (appWidgetInfo.configure == null)) {
+ // If the ID is already valid, verify if we need to configure or not.
+ item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
+ LauncherModel.updateItemInDatabase(this, item);
+ }
}
if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
@@ -3957,6 +3948,8 @@ public class Launcher extends Activity
}
item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+ item.minSpanX = appWidgetInfo.minSpanX;
+ item.minSpanY = appWidgetInfo.minSpanY;
} else {
appWidgetInfo = null;
PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
@@ -4079,7 +4072,8 @@ public class Launcher extends Activity
private boolean canRunNewAppsAnimation() {
long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
- return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
+ return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000)
+ && (mClings == null || !mClings.isVisible());
}
private ValueAnimator createNewAppBounceAnimation(View v, int i) {
@@ -4101,7 +4095,7 @@ public class Launcher extends Activity
return mDeviceProfile.getSearchBarBounds(Utilities.isRtl(getResources()));
}
- public void bindSearchablesChanged() {
+ public void bindSearchProviderChanged() {
if (mSearchDropTargetBar == null) {
return;
}
@@ -4324,13 +4318,14 @@ public class Launcher extends Activity
return oriMap[(d.getRotation() + indexOffset) % 4];
}
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public void lockScreenOrientation() {
if (mRotationEnabled) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ if (Utilities.ATLEAST_JB_MR2) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+ } else {
setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
.getConfiguration().orientation));
- } else {
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
}
}
}
@@ -4505,6 +4500,7 @@ public class Launcher extends Activity
// launcher2). Otherwise, we prompt the user upon started for migration
LauncherClings launcherClings = new LauncherClings(this);
if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
+ mClings = launcherClings;
if (mModel.canMigrateFromOldLauncherDb(this)) {
launcherClings.showMigrationCling();
} else {
@@ -4517,14 +4513,16 @@ public class Launcher extends Activity
if (mWorkspace != null) mWorkspace.setAlpha(1f);
if (mHotseat != null) mHotseat.setAlpha(1f);
if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
- if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
+ if (mSearchDropTargetBar != null) mSearchDropTargetBar.animateToState(
+ SearchDropTargetBar.State.SEARCH_BAR, 0);
}
void hideWorkspaceSearchAndHotseat() {
if (mWorkspace != null) mWorkspace.setAlpha(0f);
if (mHotseat != null) mHotseat.setAlpha(0f);
if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
- if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
+ if (mSearchDropTargetBar != null) mSearchDropTargetBar.animateToState(
+ SearchDropTargetBar.State.INVISIBLE, 0);
}
// TODO: These method should be a part of LauncherSearchCallback
@@ -4532,7 +4530,7 @@ public class Launcher extends Activity
public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
// Called from search suggestion
UserHandleCompat user = null;
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
UserHandle userHandle = appLaunchIntent.getParcelableExtra(Intent.EXTRA_USER);
if (userHandle != null) {
user = UserHandleCompat.fromUser(userHandle);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 0b7b1fdc4..d87ad67e5 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,15 +17,16 @@
package com.android.launcher3;
import android.app.SearchManager;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.UserManager;
import android.util.Log;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.util.Thunk;
import java.lang.ref.WeakReference;
@@ -96,12 +97,12 @@ public class LauncherAppState {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
- filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
// For handling managed profiles
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
sContext.registerReceiver(mModel, filter);
+ UserManagerCompat.getInstance(sContext).enableAndResetCache();
}
/**
@@ -126,7 +127,7 @@ public class LauncherAppState {
LauncherModel setLauncher(Launcher launcher) {
getLauncherProvider().setLauncherProviderChangeListener(launcher);
mModel.initialize(launcher);
- mAccessibilityDelegate = ((launcher != null) && Utilities.isLmpOrAbove()) ?
+ mAccessibilityDelegate = ((launcher != null) && Utilities.ATLEAST_LOLLIPOP) ?
new LauncherAccessibilityDelegate(launcher) : null;
return mModel;
}
@@ -147,7 +148,7 @@ public class LauncherAppState {
sLauncherProvider = new WeakReference<LauncherProvider>(provider);
}
- static LauncherProvider getLauncherProvider() {
+ public static LauncherProvider getLauncherProvider() {
return sLauncherProvider.get();
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index de7c61000..6c3a1e896 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -95,8 +95,6 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
}
protected void onProvidersChanged() {
- mLauncher.getModel().loadAndBindWidgetsAndShortcuts(mLauncher, mLauncher,
- true /* refresh */);
if (!mProviderChangeListeners.isEmpty()) {
for (Runnable callback : new ArrayList<>(mProviderChangeListeners)) {
callback.run();
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index aad18b578..882f7e202 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -68,10 +68,6 @@ public class LauncherAppWidgetInfo extends ItemInfo {
ComponentName providerName;
- // TODO: Are these necessary here?
- int minWidth = -1;
- int minHeight = -1;
-
/**
* Indicates the restore status of the widget.
*/
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 85af92f30..71a08a853 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -1,10 +1,13 @@
package com.android.launcher3;
import android.annotation.TargetApi;
+import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
@@ -19,10 +22,10 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo {
public boolean isCustomWidget = false;
- private int mSpanX = -1;
- private int mSpanY = -1;
- private int mMinSpanX = -1;
- private int mMinSpanY = -1;
+ public int spanX;
+ public int spanY;
+ public int minSpanX;
+ public int minSpanY;
public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
AppWidgetProviderInfo info) {
@@ -41,6 +44,7 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo {
public LauncherAppWidgetProviderInfo(Parcel in) {
super(in);
+ initSpans();
}
public LauncherAppWidgetProviderInfo(Context context, CustomAppWidget widget) {
@@ -52,6 +56,41 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo {
previewImage = widget.getPreviewImage();
initialLayout = widget.getWidgetLayout();
resizeMode = widget.getResizeMode();
+ initSpans();
+ }
+
+ private void initSpans() {
+ LauncherAppState app = LauncherAppState.getInstance();
+ InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
+
+ // We only care out the cell size, which is independent of the the layout direction.
+ Rect paddingLand = idp.landscapeProfile.getWorkspacePadding(false /* isLayoutRtl */);
+ Rect paddingPort = idp.portraitProfile.getWorkspacePadding(false /* isLayoutRtl */);
+
+ // Always assume we're working with the smallest span to make sure we
+ // reserve enough space in both orientations.
+ float smallestCellWidth = DeviceProfile.calculateCellWidth(Math.min(
+ idp.landscapeProfile.widthPx - paddingLand.left - paddingLand.right,
+ idp.portraitProfile.widthPx - paddingPort.left - paddingPort.right),
+ idp.numColumns);
+ float smallestCellHeight = DeviceProfile.calculateCellWidth(Math.min(
+ idp.landscapeProfile.heightPx - paddingLand.top - paddingLand.bottom,
+ idp.portraitProfile.heightPx - paddingPort.top - paddingPort.bottom),
+ idp.numRows);
+
+ // We want to account for the extra amount of padding that we are adding to the widget
+ // to ensure that it gets the full amount of space that it has requested.
+ Rect widgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(
+ app.getContext(), provider, null);
+ spanX = Math.max(1, (int) Math.ceil(
+ (minWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
+ spanY = Math.max(1, (int) Math.ceil(
+ (minHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
+
+ minSpanX = Math.max(1, (int) Math.ceil(
+ (minResizeWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
+ minSpanY = Math.max(1, (int) Math.ceil(
+ (minResizeHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -79,35 +118,9 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo {
provider.toString(), provider.getPackageName(), provider.getShortClassName(), getLabel(pm));
}
- public int getSpanX(Launcher launcher) {
- lazyLoadSpans(launcher);
- return mSpanX;
- }
-
- public int getSpanY(Launcher launcher) {
- lazyLoadSpans(launcher);
- return mSpanY;
- }
-
- public int getMinSpanX(Launcher launcher) {
- lazyLoadSpans(launcher);
- return mMinSpanX;
- }
-
- public int getMinSpanY(Launcher launcher) {
- lazyLoadSpans(launcher);
- return mMinSpanY;
- }
-
- private void lazyLoadSpans(Launcher launcher) {
- if (mSpanX < 0 || mSpanY < 0 || mMinSpanX < 0 || mMinSpanY < 0) {
- int[] minResizeSpan = launcher.getMinSpanForWidget(this);
- int[] span = launcher.getSpanForWidget(this);
-
- mSpanX = span[0];
- mSpanY = span[1];
- mMinSpanX = minResizeSpan[0];
- mMinSpanY = minResizeSpan[1];
- }
+ public Point getMinSpans(InvariantDeviceProfile idp, Context context) {
+ return new Point(
+ (resizeMode & RESIZE_HORIZONTAL) != 0 ? minSpanX : -1,
+ (resizeMode & RESIZE_VERTICAL) != 0 ? minSpanY : -1);
}
}
diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java
index a92a889f9..8eb4e6369 100644
--- a/src/com/android/launcher3/LauncherBackupAgentHelper.java
+++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java
@@ -24,6 +24,8 @@ import android.database.Cursor;
import android.os.ParcelFileDescriptor;
import android.util.Log;
+import com.android.launcher3.model.MigrateFromRestoreTask;
+
import java.io.IOException;
public class LauncherBackupAgentHelper extends BackupAgentHelper {
@@ -63,7 +65,7 @@ public class LauncherBackupAgentHelper extends BackupAgentHelper {
@Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
- if (!Utilities.isLmpOrAbove()) {
+ if (!Utilities.ATLEAST_LOLLIPOP) {
// No restore for old devices.
Log.i(TAG, "You shall not pass!!!");
Log.d(TAG, "Restore is only supported on devices running Lollipop and above.");
@@ -91,11 +93,19 @@ public class LauncherBackupAgentHelper extends BackupAgentHelper {
LauncherAppState.getLauncherProvider().clearFlagEmptyDbCreated();
LauncherClings.synchonouslyMarkFirstRunClingDismissed(this);
- // TODO: Update the backup set to include rank.
+ // Rank was added in v4.
if (mHelper.restoredBackupVersion <= 3) {
LauncherAppState.getLauncherProvider().updateFolderItemsRank();
- LauncherAppState.getLauncherProvider().convertShortcutsToLauncherActivities();
}
+
+ if (MigrateFromRestoreTask.ENABLED && mHelper.shouldAttemptWorkspaceMigration()) {
+ MigrateFromRestoreTask.markForMigration(getApplicationContext(),
+ (int) mHelper.migrationCompatibleProfileData.desktopCols,
+ (int) mHelper.migrationCompatibleProfileData.desktopRows,
+ mHelper.widgetSizes);
+ }
+
+ LauncherAppState.getLauncherProvider().convertShortcutsToLauncherActivities();
} else {
if (VERBOSE) Log.v(TAG, "Nothing was restored, clearing DB");
LauncherAppState.getLauncherProvider().createEmptyDB();
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index 8c6fedbdc..2d11d3af1 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -32,6 +32,7 @@ import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
@@ -51,6 +52,7 @@ import com.android.launcher3.backup.BackupProtos.Screen;
import com.android.launcher3.backup.BackupProtos.Widget;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.model.MigrateFromRestoreTask;
import com.android.launcher3.util.Thunk;
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
import com.google.protobuf.nano.MessageNano;
@@ -75,7 +77,7 @@ public class LauncherBackupHelper implements BackupHelper {
private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
private static final boolean DEBUG = LauncherBackupAgentHelper.DEBUG;
- private static final int BACKUP_VERSION = 3;
+ private static final int BACKUP_VERSION = 4;
private static final int MAX_JOURNAL_SIZE = 1000000;
// Journal key is such that it is always smaller than any dynamically generated
@@ -107,6 +109,7 @@ public class LauncherBackupHelper implements BackupHelper {
Favorites.SPANY, // 15
Favorites.TITLE, // 16
Favorites.PROFILE_ID, // 17
+ Favorites.RANK, // 18
};
private static final int ID_INDEX = 0;
@@ -126,6 +129,7 @@ public class LauncherBackupHelper implements BackupHelper {
private static final int SPANX_INDEX = 14;
private static final int SPANY_INDEX = 15;
private static final int TITLE_INDEX = 16;
+ private static final int RANK_INDEX = 18;
private static final String[] SCREEN_PROJECTION = {
WorkspaceScreens._ID, // 0
@@ -150,9 +154,16 @@ public class LauncherBackupHelper implements BackupHelper {
private DeviceProfieData mDeviceProfileData;
private InvariantDeviceProfile mIdp;
+ DeviceProfieData migrationCompatibleProfileData;
+ HashSet<String> widgetSizes = new HashSet<>();
+
boolean restoreSuccessful;
int restoredBackupVersion = 1;
+ // When migrating from a device which different hotseat configuration, the icons are shifted
+ // to center along the new all-apps icon.
+ private int mHotseatShift = 0;
+
public LauncherBackupHelper(Context context) {
mContext = context;
mExistingKeys = new HashSet<String>();
@@ -282,17 +293,39 @@ public class LauncherBackupHelper implements BackupHelper {
return true;
}
- boolean isHotsetCompatible = false;
+ boolean isHotseatCompatible = false;
if (currentProfile.allappsRank >= oldProfile.hotseatCount) {
- isHotsetCompatible = true;
+ isHotseatCompatible = true;
+ mHotseatShift = 0;
+ }
+
+ if ((currentProfile.allappsRank >= oldProfile.allappsRank)
+ && ((currentProfile.hotseatCount - currentProfile.allappsRank) >=
+ (oldProfile.hotseatCount - oldProfile.allappsRank))) {
+ // There is enough space on both sides of the hotseat.
+ isHotseatCompatible = true;
+ mHotseatShift = currentProfile.allappsRank - oldProfile.allappsRank;
+ }
+
+ if (!isHotseatCompatible) {
+ return false;
}
- if ((currentProfile.hotseatCount >= oldProfile.hotseatCount) &&
- (currentProfile.allappsRank == oldProfile.allappsRank)) {
- isHotsetCompatible = true;
+ if ((currentProfile.desktopCols >= oldProfile.desktopCols)
+ && (currentProfile.desktopRows >= oldProfile.desktopRows)) {
+ return true;
}
- return isHotsetCompatible && (currentProfile.desktopCols >= oldProfile.desktopCols)
- && (currentProfile.desktopRows >= oldProfile.desktopRows);
+ if (MigrateFromRestoreTask.ENABLED &&
+ (oldProfile.desktopCols - currentProfile.desktopCols <= 1) &&
+ (oldProfile.desktopRows - currentProfile.desktopRows <= 1)) {
+ // Allow desktop migration when row and/or column count contracts by 1.
+
+ migrationCompatibleProfileData = initDeviceProfileData(mIdp);
+ migrationCompatibleProfileData.desktopCols = oldProfile.desktopCols;
+ migrationCompatibleProfileData.desktopRows = oldProfile.desktopRows;
+ return true;
+ }
+ return false;
}
/**
@@ -432,7 +465,10 @@ public class LauncherBackupHelper implements BackupHelper {
Key key = getKey(Key.FAVORITE, id);
mKeys.add(key);
final String backupKey = keyToBackupKey(key);
- if (!mExistingKeys.contains(backupKey) || updateTime >= mLastBackupRestoreTime) {
+
+ // Favorite proto changed in v4. Backup again if the version is old.
+ if (!mExistingKeys.contains(backupKey) || updateTime >= mLastBackupRestoreTime
+ || restoredBackupVersion < 4) {
writeRowToBackup(key, packFavorite(cursor), data);
} else {
if (DEBUG) Log.d(TAG, "favorite already backup up: " + id);
@@ -601,10 +637,11 @@ public class LauncherBackupHelper implements BackupHelper {
Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
if (icon == null) {
Log.w(TAG, "failed to unpack icon for " + key.name);
+ } else {
+ if (VERBOSE) Log.v(TAG, "saving restored icon as: " + key.name);
+ mIconCache.preloadIcon(ComponentName.unflattenFromString(key.name), icon, res.dpi,
+ "" /* label */, mUserSerial, mIdp);
}
- if (VERBOSE) Log.v(TAG, "saving restored icon as: " + key.name);
- mIconCache.preloadIcon(ComponentName.unflattenFromString(key.name), icon, res.dpi,
- "" /* label */, mUserSerial);
}
/**
@@ -638,7 +675,9 @@ public class LauncherBackupHelper implements BackupHelper {
} else {
Log.w(TAG, "empty intent on appwidget: " + id);
}
- if (mExistingKeys.contains(backupKey) && restoredBackupVersion >= BACKUP_VERSION) {
+
+ // Widget backup proto changed in v3. So add it again if the original backup is old.
+ if (mExistingKeys.contains(backupKey) && restoredBackupVersion >= 3) {
if (DEBUG) Log.d(TAG, "already saved widget " + backupKey);
// remember that we already backed this up previously
@@ -685,11 +724,12 @@ public class LauncherBackupHelper implements BackupHelper {
Log.w(TAG, "failed to unpack widget icon for " + key.name);
} else {
mIconCache.preloadIcon(ComponentName.unflattenFromString(widget.provider),
- icon, widget.icon.dpi, widget.label, mUserSerial);
+ icon, widget.icon.dpi, widget.label, mUserSerial, mIdp);
}
}
- // future site of widget table mutation
+ // Cache widget min sizes incase migration is required.
+ widgetSizes.add(widget.provider + "#" + widget.minSpanX + "," + widget.minSpanY);
}
/** create a new key, with an integer ID.
@@ -773,6 +813,7 @@ public class LauncherBackupHelper implements BackupHelper {
favorite.spanX = c.getInt(SPANX_INDEX);
favorite.spanY = c.getInt(SPANY_INDEX);
favorite.iconType = c.getInt(ICON_TYPE_INDEX);
+ favorite.rank = c.getInt(RANK_INDEX);
String title = c.getString(TITLE_INDEX);
if (!TextUtils.isEmpty(title)) {
@@ -847,6 +888,11 @@ public class LauncherBackupHelper implements BackupHelper {
throws IOException {
Favorite favorite = unpackProto(new Favorite(), buffer, dataSize);
+ // If it is a hotseat item, move it accordingly.
+ if (favorite.container == Favorites.CONTAINER_HOTSEAT) {
+ favorite.screen += mHotseatShift;
+ }
+
ContentValues values = new ContentValues();
values.put(Favorites._ID, favorite.id);
values.put(Favorites.SCREEN, favorite.screen);
@@ -855,6 +901,7 @@ public class LauncherBackupHelper implements BackupHelper {
values.put(Favorites.CELLY, favorite.cellY);
values.put(Favorites.SPANX, favorite.spanX);
values.put(Favorites.SPANY, favorite.spanY);
+ values.put(Favorites.RANK, favorite.rank);
if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
values.put(Favorites.ICON_TYPE, favorite.iconType);
@@ -880,7 +927,11 @@ public class LauncherBackupHelper implements BackupHelper {
UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
- DeviceProfieData currentProfile = mDeviceProfileData;
+ // If we will attempt grid resize, use the original profile to validate grid size, as
+ // anything which fits in the original grid should fit in the current grid after
+ // grid migration.
+ DeviceProfieData currentProfile = migrationCompatibleProfileData == null
+ ? mDeviceProfileData : migrationCompatibleProfileData;
if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
if (!TextUtils.isEmpty(favorite.appWidgetProvider)) {
@@ -972,14 +1023,9 @@ public class LauncherBackupHelper implements BackupHelper {
widget.icon.dpi = dpi;
}
- // Calculate the spans corresponding to any one of the orientations as it should not change
- // based on orientation.
- int[] minSpans = CellLayout.rectToCell(
- mIdp.portraitProfile, mContext, info.minResizeWidth, info.minResizeHeight, null);
- widget.minSpanX = (info.resizeMode & LauncherAppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0
- ? minSpans[0] : -1;
- widget.minSpanY = (info.resizeMode & LauncherAppWidgetProviderInfo.RESIZE_VERTICAL) != 0
- ? minSpans[1] : -1;
+ Point spans = info.getMinSpans(mIdp, mContext);
+ widget.minSpanX = spans.x;
+ widget.minSpanY = spans.y;
return widget;
}
@@ -1164,6 +1210,10 @@ public class LauncherBackupHelper implements BackupHelper {
}
}
+ public boolean shouldAttemptWorkspaceMigration() {
+ return migrationCompatibleProfileData != null;
+ }
+
/**
* A class to check if an activity can handle one of the intents from a list of
* predefined intents.
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 6618cca78..e34bd57fd 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -77,6 +77,7 @@ public interface LauncherCallbacks {
public boolean providesSearch();
public boolean startSearch(String initialQuery, boolean selectInitialQuery,
Bundle appSearchData, Rect sourceBounds);
+ public boolean startSearchFromAllApps(String query);
@Deprecated
public void startVoice();
public boolean hasCustomContentToLeft();
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
index c13752cb1..18fe8ef86 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -16,8 +16,6 @@
package com.android.launcher3;
-import android.accounts.Account;
-import android.accounts.AccountManager;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.annotation.TargetApi;
@@ -36,6 +34,7 @@ import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.accessibility.AccessibilityManager;
+
import com.android.launcher3.util.Thunk;
class LauncherClings implements OnClickListener {
@@ -44,8 +43,6 @@ class LauncherClings implements OnClickListener {
private static final String TAG_CROP_TOP_AND_SIDES = "crop_bg_top_and_sides";
- private static final boolean DISABLE_CLINGS = false;
-
private static final int SHOW_CLING_DURATION = 250;
private static final int DISMISS_CLING_DURATION = 200;
@@ -54,6 +51,7 @@ class LauncherClings implements OnClickListener {
@Thunk Launcher mLauncher;
private LayoutInflater mInflater;
+ @Thunk boolean mIsVisible;
/** Ctor */
public LauncherClings(Launcher launcher) {
@@ -94,6 +92,7 @@ class LauncherClings implements OnClickListener {
* package was not preinstalled and there exists a db to migrate from.
*/
public void showMigrationCling() {
+ mIsVisible = true;
mLauncher.hideWorkspaceSearchAndHotseat();
ViewGroup root = (ViewGroup) mLauncher.findViewById(R.id.launcher);
@@ -120,6 +119,7 @@ class LauncherClings implements OnClickListener {
}
public void showLongPressCling(boolean showWelcome) {
+ mIsVisible = true;
ViewGroup root = (ViewGroup) mLauncher.findViewById(R.id.launcher);
View cling = mInflater.inflate(R.layout.longpress_cling, root, false);
@@ -199,6 +199,7 @@ class LauncherClings implements OnClickListener {
mLauncher.getSharedPrefs().edit()
.putBoolean(flag, true)
.apply();
+ mIsVisible = false;
if (postAnimationCb != null) {
postAnimationCb.run();
}
@@ -212,13 +213,13 @@ class LauncherClings implements OnClickListener {
}
}
+ public boolean isVisible() {
+ return mIsVisible;
+ }
+
/** Returns whether the clings are enabled or should be shown */
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private boolean areClingsEnabled() {
- if (DISABLE_CLINGS) {
- return false;
- }
-
// disable clings when running in a test harness
if(ActivityManager.isRunningInTestHarness()) return false;
@@ -231,10 +232,7 @@ class LauncherClings implements OnClickListener {
// Restricted secondary users (child mode) will potentially have very few apps
// seeded when they start up for the first time. Clings won't work well with that
- boolean supportsLimitedUsers =
- android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
- Account[] accounts = AccountManager.get(mLauncher).getAccounts();
- if (supportsLimitedUsers && accounts.length == 0) {
+ if (Utilities.ATLEAST_JB_MR2) {
UserManager um = (UserManager) mLauncher.getSystemService(Context.USER_SERVICE);
Bundle restrictions = um.getUserRestrictions();
if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b60477fa0..b5922c6a3 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -17,6 +17,7 @@
package com.android.launcher3;
import android.app.SearchManager;
+import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -33,7 +34,6 @@ import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
-import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
@@ -55,6 +55,7 @@ import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.model.MigrateFromRestoreTask;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.CursorIconInfo;
@@ -203,7 +204,7 @@ public class LauncherModel extends BroadcastReceiver
public void bindComponentsRemoved(ArrayList<String> packageNames,
ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
public void bindAllPackages(WidgetsModel model);
- public void bindSearchablesChanged();
+ public void bindSearchProviderChanged();
public boolean isAllAppsButtonRank(int rank);
public void onPageBoundSynchronously(int page);
public void dumpLogsToLocalData();
@@ -257,7 +258,7 @@ public class LauncherModel extends BroadcastReceiver
/** Runs the specified runnable immediately if called from the worker thread, otherwise it is
* posted on the worker thread handler. */
- private static void runOnWorkerThread(Runnable r) {
+ @Thunk static void runOnWorkerThread(Runnable r) {
if (sWorkerThread.getThreadId() == Process.myTid()) {
r.run();
} else {
@@ -266,19 +267,6 @@ public class LauncherModel extends BroadcastReceiver
}
}
- /**
- * Runs the specified runnable after the loader is complete
- */
- @Thunk void runAfterBindCompletes(Runnable r) {
- if (isLoadingWorkspace() || !mHasLoaderCompletedOnce) {
- synchronized (mBindCompleteRunnables) {
- mBindCompleteRunnables.add(r);
- }
- } else {
- runOnWorkerThread(r);
- }
- }
-
boolean canMigrateFromOldLauncherDb(Launcher launcher) {
return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
}
@@ -892,8 +880,13 @@ public class LauncherModel extends BroadcastReceiver
}
private void assertWorkspaceLoaded() {
- if (LauncherAppState.isDogfoodBuild() && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
- throw new RuntimeException("Trying to add shortcut while loader is running");
+ if (LauncherAppState.isDogfoodBuild()) {
+ synchronized (mLock) {
+ if (!mHasLoaderCompletedOnce ||
+ (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) {
+ throw new RuntimeException("Trying to add shortcut while loader is running");
+ }
+ }
}
}
@@ -1133,7 +1126,7 @@ public class LauncherModel extends BroadcastReceiver
* Update the order of the workspace screens in the database. The array list contains
* a list of screen ids in the order that they should appear.
*/
- void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+ public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
final ContentResolver cr = context.getContentResolver();
final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -1280,14 +1273,14 @@ public class LauncherModel extends BroadcastReceiver
if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
// If we have changed locale we need to clear out the labels in all apps/workspace.
forceReload();
- } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
- SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
+ } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action)) {
Callbacks callbacks = getCallback();
if (callbacks != null) {
- callbacks.bindSearchablesChanged();
+ callbacks.bindSearchProviderChanged();
}
} else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
|| LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
+ UserManagerCompat.getInstance(context).enableAndResetCache();
forceReload();
}
}
@@ -1388,16 +1381,6 @@ public class LauncherModel extends BroadcastReceiver
mHandler.post(r);
}
}
-
- // Run all the bind complete runnables after workspace is bound.
- if (!mBindCompleteRunnables.isEmpty()) {
- synchronized (mBindCompleteRunnables) {
- for (final Runnable r : mBindCompleteRunnables) {
- runOnWorkerThread(r);
- }
- mBindCompleteRunnables.clear();
- }
- }
}
public void stopLoader() {
@@ -1411,7 +1394,7 @@ public class LauncherModel extends BroadcastReceiver
/**
* Loads the workspace screen ids in an ordered list.
*/
- @Thunk static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
+ public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
final ContentResolver contentResolver = context.getContentResolver();
final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -1439,15 +1422,6 @@ public class LauncherModel extends BroadcastReceiver
return mAllAppsLoaded;
}
- boolean isLoadingWorkspace() {
- synchronized (mLock) {
- if (mLoaderTask != null) {
- return mLoaderTask.isLoadingWorkspace();
- }
- }
- return false;
- }
-
/**
* Runnable for the thread that loads the contents of the launcher:
* - workspace icons
@@ -1466,10 +1440,6 @@ public class LauncherModel extends BroadcastReceiver
mFlags = flags;
}
- boolean isLoadingWorkspace() {
- return mIsLoadingAndBindingWorkspace;
- }
-
private void loadAndBindWorkspace() {
mIsLoadingAndBindingWorkspace = true;
@@ -1641,11 +1611,12 @@ public class LauncherModel extends BroadcastReceiver
}
// check & update map of what's occupied; used to discard overlapping/invalid items
- private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item) {
+ private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item,
+ ArrayList<Long> workspaceScreens) {
LauncherAppState app = LauncherAppState.getInstance();
InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
- final int countX = (int) profile.numColumns;
- final int countY = (int) profile.numRows;
+ final int countX = profile.numColumns;
+ final int countY = profile.numRows;
long containerIndex = item.screenId;
if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
@@ -1687,7 +1658,12 @@ public class LauncherModel extends BroadcastReceiver
occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
return true;
}
- } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ if (!workspaceScreens.contains((Long) item.screenId)) {
+ // The item has an invalid screen id.
+ return false;
+ }
+ } else {
// Skip further checking if it is not the hotseat or workspace container
return true;
}
@@ -1754,8 +1730,27 @@ public class LauncherModel extends BroadcastReceiver
LauncherAppState app = LauncherAppState.getInstance();
InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
- int countX = (int) profile.numColumns;
- int countY = (int) profile.numRows;
+ int countX = profile.numColumns;
+ int countY = profile.numRows;
+
+ if (MigrateFromRestoreTask.ENABLED && MigrateFromRestoreTask.shouldRunTask(mContext)) {
+ long migrationStartTime = System.currentTimeMillis();
+ Log.v(TAG, "Starting workspace migration after restore");
+ try {
+ MigrateFromRestoreTask task = new MigrateFromRestoreTask(mContext);
+ // Clear the flags before starting the task, so that we do not run the task
+ // again, in case there was an uncaught error.
+ MigrateFromRestoreTask.clearFlags(mContext);
+ task.execute();
+ } catch (Exception e) {
+ Log.e(TAG, "Error during grid migration", e);
+
+ // Clear workspace.
+ mFlags = mFlags | LOADER_FLAG_CLEAR_WORKSPACE;
+ }
+ Log.v(TAG, "Workspace migration completed in "
+ + (System.currentTimeMillis() - migrationStartTime));
+ }
if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
@@ -1776,6 +1771,7 @@ public class LauncherModel extends BroadcastReceiver
clearSBgDataStructures();
final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
.getInstance(mContext).updateAndGetActiveSessionCache();
+ sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
final ArrayList<Long> restoredRows = new ArrayList<Long>();
@@ -1977,6 +1973,7 @@ public class LauncherModel extends BroadcastReceiver
} catch (URISyntaxException e) {
Launcher.addDumpLog(TAG,
"Invalid uri: " + intentDescription, true);
+ itemsToRemove.add(id);
continue;
}
@@ -2047,7 +2044,7 @@ public class LauncherModel extends BroadcastReceiver
}
// check & update map of what's occupied
- if (!checkItemPlacement(occupied, info)) {
+ if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
itemsToRemove.add(id);
break;
}
@@ -2098,7 +2095,7 @@ public class LauncherModel extends BroadcastReceiver
folderInfo.options = c.getInt(optionsIndex);
// check & update map of what's occupied
- if (!checkItemPlacement(occupied, folderInfo)) {
+ if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) {
itemsToRemove.add(id);
break;
}
@@ -2163,14 +2160,18 @@ public class LauncherModel extends BroadcastReceiver
appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
provider.provider);
- int status = restoreStatus;
+ // The provider is available. So the widget is either
+ // available or not available. We do not need to track
+ // any future restore updates.
+ int status = restoreStatus &
+ ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
if (!wasProviderReady) {
// If provider was not previously ready, update the
// status and UI flag.
// Id would be valid only if the widget restore broadcast was received.
if (isIdValid) {
- status = LauncherAppWidgetInfo.RESTORE_COMPLETED;
+ status = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
} else {
status &= ~LauncherAppWidgetInfo
.FLAG_PROVIDER_NOT_READY;
@@ -2214,13 +2215,14 @@ public class LauncherModel extends BroadcastReceiver
if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
Log.e(TAG, "Widget found where container != " +
- "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
+ "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
+ itemsToRemove.add(id);
continue;
}
appWidgetInfo.container = container;
// check & update map of what's occupied
- if (!checkItemPlacement(occupied, appWidgetInfo)) {
+ if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) {
itemsToRemove.add(id);
break;
}
@@ -2279,6 +2281,21 @@ public class LauncherModel extends BroadcastReceiver
}
}
+ // Sort all the folder items and make sure the first 3 items are high resolution.
+ for (FolderInfo folder : sBgFolders) {
+ Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
+ int pos = 0;
+ for (ShortcutInfo info : folder.contents) {
+ if (info.usingLowResIcon) {
+ info.updateIcon(mIconCache, false);
+ }
+ pos ++;
+ if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
+ break;
+ }
+ }
+ }
+
if (restoredRows.size() > 0) {
// Update restored items that no longer require special handling
ContentValues values = new ContentValues();
@@ -2294,8 +2311,6 @@ public class LauncherModel extends BroadcastReceiver
null, sWorker);
}
- sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
-
// Remove any empty screens
ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
for (ItemInfo item: sBgItemsIdMap) {
@@ -2657,13 +2672,24 @@ public class LauncherModel extends BroadcastReceiver
callbacks.finishBindingItems();
}
+ mIsLoadingAndBindingWorkspace = false;
+
+ // Run all the bind complete runnables after workspace is bound.
+ if (!mBindCompleteRunnables.isEmpty()) {
+ synchronized (mBindCompleteRunnables) {
+ for (final Runnable r : mBindCompleteRunnables) {
+ runOnWorkerThread(r);
+ }
+ mBindCompleteRunnables.clear();
+ }
+ }
+
// If we're profiling, ensure this is the last thing in the queue.
if (DEBUG_LOADERS) {
Log.d(TAG, "bound workspace in "
+ (SystemClock.uptimeMillis()-t) + "ms");
}
- mIsLoadingAndBindingWorkspace = false;
}
};
if (isLoadingSynchronously) {
@@ -2792,12 +2818,27 @@ public class LauncherModel extends BroadcastReceiver
final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
if (heuristic != null) {
- runAfterBindCompletes(new Runnable() {
+ final Runnable r = new Runnable() {
@Override
public void run() {
heuristic.processUserApps(apps);
}
+ };
+ runOnMainThread(new Runnable() {
+
+ @Override
+ public void run() {
+ // Check isLoadingWorkspace on the UI thread, as it is updated on
+ // the UI thread.
+ if (mIsLoadingAndBindingWorkspace) {
+ synchronized (mBindCompleteRunnables) {
+ mBindCompleteRunnables.add(r);
+ }
+ } else {
+ runOnWorkerThread(r);
+ }
+ }
});
}
}
@@ -2825,8 +2866,7 @@ public class LauncherModel extends BroadcastReceiver
// Cleanup any data stored for a deleted user.
ManagedProfileHeuristic.processAllUsers(profiles, mContext);
- loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks),
- true /* refresh */);
+ loadAndBindWidgetsAndShortcuts(tryGetCallbacks(oldCallbacks), true /* refresh */);
if (DEBUG_LOADERS) {
Log.d(TAG, "Icons processed in "
+ (SystemClock.uptimeMillis() - loadTime) + "ms");
@@ -2895,7 +2935,7 @@ public class LauncherModel extends BroadcastReceiver
}
// Reload widget list. No need to refresh, as we only want to update the icons and labels.
- loadAndBindWidgetsAndShortcuts(mApp.getContext(), callbacks, false);
+ loadAndBindWidgetsAndShortcuts(callbacks, false);
}
void enqueuePackageUpdated(PackageUpdatedTask task) {
@@ -3155,7 +3195,15 @@ public class LauncherModel extends BroadcastReceiver
if (mUser.equals(widgetInfo.user)
&& widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
&& packageSet.contains(widgetInfo.providerName.getPackageName())) {
- widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
+ widgetInfo.restoreStatus &=
+ ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
+ ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+
+ // adding this flag ensures that launcher shows 'click to setup'
+ // if the widget has a config activity. In case there is no config
+ // activity, it will be marked as 'restored' during bind.
+ widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+
widgets.add(widgetInfo);
updateItemInDatabase(context, widgetInfo);
}
@@ -3235,8 +3283,36 @@ public class LauncherModel extends BroadcastReceiver
});
}
- // onProvidersChanged method (API >= 17) already refreshed the widget list
- loadAndBindWidgetsAndShortcuts(context, callbacks, Build.VERSION.SDK_INT < 17);
+ // Update widgets
+ if (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE) {
+ // Always refresh for a package event on secondary user
+ boolean needToRefresh = !mUser.equals(UserHandleCompat.myUserHandle());
+
+ // Refresh widget list, if the package already had a widget.
+ synchronized (sBgLock) {
+ if (sBgWidgetProviders != null) {
+ HashSet<String> pkgSet = new HashSet<>();
+ Collections.addAll(pkgSet, mPackages);
+
+ for (ComponentKey key : sBgWidgetProviders.keySet()) {
+ needToRefresh |= key.user.equals(mUser) &&
+ pkgSet.contains(key.componentName.getPackageName());
+ }
+ }
+ }
+
+ if (!needToRefresh && mOp != OP_REMOVE) {
+ // Refresh widget list, if there is any newly added widget
+ PackageManager pm = context.getPackageManager();
+ for (String pkg : mPackages) {
+ needToRefresh |= !pm.queryBroadcastReceivers(
+ new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
+ .setPackage(pkg), 0).isEmpty();
+ }
+ }
+
+ loadAndBindWidgetsAndShortcuts(callbacks, needToRefresh);
+ }
// Write all the logs to disk
mHandler.post(new Runnable() {
@@ -3310,13 +3386,12 @@ public class LauncherModel extends BroadcastReceiver
}
}
- public void loadAndBindWidgetsAndShortcuts(final Context context, final Callbacks callbacks,
- final boolean refresh) {
+ public void loadAndBindWidgetsAndShortcuts(final Callbacks callbacks, final boolean refresh) {
runOnWorkerThread(new Runnable() {
@Override
public void run() {
- updateWidgetsModel(context, refresh);
+ updateWidgetsModel(refresh);
final WidgetsModel model = mBgWidgetsModel.clone();
mHandler.post(new Runnable() {
@@ -3340,10 +3415,10 @@ public class LauncherModel extends BroadcastReceiver
*
* @see #loadAndBindWidgetsAndShortcuts
*/
- @Thunk void updateWidgetsModel(Context context, boolean refresh) {
- PackageManager packageManager = context.getPackageManager();
+ @Thunk void updateWidgetsModel(boolean refresh) {
+ PackageManager packageManager = mApp.getContext().getPackageManager();
final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
- widgetsAndShortcuts.addAll(getWidgetProviders(context, refresh));
+ widgetsAndShortcuts.addAll(getWidgetProviders(mApp.getContext(), refresh));
Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index cc5e18bc1..8791e9e57 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -65,20 +65,17 @@ import java.util.HashSet;
import java.util.List;
public class LauncherProvider extends ContentProvider {
- private static final String TAG = "Launcher.LauncherProvider";
+ private static final String TAG = "LauncherProvider";
private static final boolean LOGD = false;
private static final int DATABASE_VERSION = 26;
- static final String OLD_AUTHORITY = "com.android.launcher2.settings";
- static final String AUTHORITY = ProviderConfig.AUTHORITY;
+ public static final String AUTHORITY = ProviderConfig.AUTHORITY;
static final String TABLE_FAVORITES = LauncherSettings.Favorites.TABLE_NAME;
static final String TABLE_WORKSPACE_SCREENS = LauncherSettings.WorkspaceScreens.TABLE_NAME;
static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
- private static final String URI_PARAM_IS_EXTERNAL_ADD = "isExternalAdd";
-
private static final String RESTRICTION_PACKAGE_NAME = "workspace.configuration.package.name";
@Thunk LauncherProviderChangeListener mListener;
@@ -140,14 +137,21 @@ public class LauncherProvider extends ContentProvider {
return db.insert(table, nullColumnHack, values);
}
+ private void reloadLauncherIfExternal() {
+ if (Utilities.ATLEAST_MARSHMALLOW && Binder.getCallingPid() != Process.myPid()) {
+ LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+ if (app != null) {
+ app.reloadWorkspace();
+ }
+ }
+ }
+
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
SqlArguments args = new SqlArguments(uri);
- // In very limited cases, we support system|signature permission apps to add to the db
- String externalAdd = uri.getQueryParameter(URI_PARAM_IS_EXTERNAL_ADD);
- final boolean isExternalAll = externalAdd != null && "true".equals(externalAdd);
- if (isExternalAll) {
+ // In very limited cases, we support system|signature permission apps to modify the db.
+ if (Binder.getCallingPid() != Process.myPid()) {
if (!mOpenHelper.initializeExternalAdd(initialValues)) {
return null;
}
@@ -161,13 +165,20 @@ public class LauncherProvider extends ContentProvider {
uri = ContentUris.withAppendedId(uri, rowId);
notifyListeners();
- if (isExternalAll) {
+ if (Utilities.ATLEAST_MARSHMALLOW) {
+ reloadLauncherIfExternal();
+ } else {
+ // Deprecated behavior to support legacy devices which rely on provider callbacks.
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
- if (app != null) {
+ if (app != null && "true".equals(uri.getQueryParameter("isExternalAdd"))) {
app.reloadWorkspace();
}
- }
+ String notify = uri.getQueryParameter("notify");
+ if (notify == null || "true".equals(notify)) {
+ getContext().getContentResolver().notifyChange(uri, null);
+ }
+ }
return uri;
}
@@ -192,6 +203,7 @@ public class LauncherProvider extends ContentProvider {
}
notifyListeners();
+ reloadLauncherIfExternal();
return values.length;
}
@@ -203,6 +215,7 @@ public class LauncherProvider extends ContentProvider {
try {
ContentProviderResult[] result = super.applyBatch(operations);
db.setTransactionSuccessful();
+ reloadLauncherIfExternal();
return result;
} finally {
db.endTransaction();
@@ -217,6 +230,7 @@ public class LauncherProvider extends ContentProvider {
int count = db.delete(args.table, args.where, args.args);
if (count > 0) notifyListeners();
+ reloadLauncherIfExternal();
return count;
}
@@ -229,6 +243,7 @@ public class LauncherProvider extends ContentProvider {
int count = db.update(args.table, values, args.where, args.args);
if (count > 0) notifyListeners();
+ reloadLauncherIfExternal();
return count;
}
@@ -399,7 +414,7 @@ public class LauncherProvider extends ContentProvider {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction() {
// UserManager.getApplicationRestrictions() requires minSdkVersion >= 18
- if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ if (!Utilities.ATLEAST_JB_MR2) {
return null;
}
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index f2c85a195..8a5804f34 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -143,7 +143,7 @@ public class LauncherSettings {
*
* @return The unique content URL for the specified row.
*/
- static Uri getContentUri(long id) {
+ public static Uri getContentUri(long id) {
return Uri.parse("content://" + ProviderConfig.AUTHORITY +
"/" + TABLE_NAME + "/" + id);
}
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index d69b7432d..cdde8c13f 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -80,13 +80,6 @@ import java.util.HashMap;
public class LauncherStateTransitionAnimation {
/**
- * Callbacks made during the state transition
- */
- interface Callbacks {
- public void onStateTransitionHideSearchBar();
- }
-
- /**
* Private callbacks made during transition setup.
*/
static abstract class PrivateTransitionCallbacks {
@@ -111,12 +104,10 @@ public class LauncherStateTransitionAnimation {
public static final int SINGLE_FRAME_DELAY = 16;
@Thunk Launcher mLauncher;
- @Thunk Callbacks mCb;
- @Thunk AnimatorSet mStateAnimation;
+ @Thunk AnimatorSet mCurrentAnimation;
- public LauncherStateTransitionAnimation(Launcher l, Callbacks cb) {
+ public LauncherStateTransitionAnimation(Launcher l) {
mLauncher = l;
- mCb = cb;
}
/**
@@ -125,8 +116,8 @@ public class LauncherStateTransitionAnimation {
* @param startSearchAfterTransition Immediately starts app search after the transition to
* All Apps is completed.
*/
- public void startAnimationToAllApps(final boolean animated,
- final boolean startSearchAfterTransition) {
+ public void startAnimationToAllApps(final Workspace.State fromWorkspaceState,
+ final boolean animated, final boolean startSearchAfterTransition) {
final AllAppsContainerView toView = mLauncher.getAppsView();
final View buttonView = mLauncher.getAllAppsButton();
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
@@ -159,15 +150,16 @@ public class LauncherStateTransitionAnimation {
}
};
// Only animate the search bar if animating from spring loaded mode back to all apps
- startAnimationToOverlay(Workspace.State.NORMAL_HIDDEN, buttonView, toView,
- toView.getContentView(), toView.getRevealView(), toView.getSearchBarView(),
- animated, true /* hideSearchBar */, cb);
+ mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
+ Workspace.State.NORMAL_HIDDEN, buttonView, toView, toView.getContentView(),
+ toView.getRevealView(), toView.getSearchBarView(), animated, cb);
}
/**
* Starts an animation to the widgets view.
*/
- public void startAnimationToWidgets(final boolean animated) {
+ public void startAnimationToWidgets(final Workspace.State fromWorkspaceState,
+ final boolean animated) {
final WidgetsContainerView toView = mLauncher.getWidgetsView();
final View buttonView = mLauncher.getWidgetsButton();
@@ -177,17 +169,17 @@ public class LauncherStateTransitionAnimation {
return 0.3f;
}
};
- startAnimationToOverlay(Workspace.State.OVERVIEW_HIDDEN, buttonView, toView,
- toView.getContentView(), toView.getRevealView(), null, animated,
- true /* hideSearchBar */, cb);
+ mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
+ Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, toView.getContentView(),
+ toView.getRevealView(), null, animated, cb);
}
/**
* Starts and animation to the workspace from the current overlay view.
*/
public void startAnimationToWorkspace(final Launcher.State fromState,
- final Workspace.State toWorkspaceState, final int toWorkspacePage,
- final boolean animated, final Runnable onCompleteRunnable) {
+ final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
+ final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) {
if (toWorkspaceState != Workspace.State.NORMAL &&
toWorkspaceState != Workspace.State.SPRING_LOADED &&
toWorkspaceState != Workspace.State.OVERVIEW) {
@@ -195,10 +187,10 @@ public class LauncherStateTransitionAnimation {
}
if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
- startAnimationToWorkspaceFromAllApps(toWorkspaceState, toWorkspacePage,
+ startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, toWorkspacePage,
animated, onCompleteRunnable);
} else {
- startAnimationToWorkspaceFromWidgets(toWorkspaceState, toWorkspacePage,
+ startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, toWorkspacePage,
animated, onCompleteRunnable);
}
}
@@ -207,12 +199,13 @@ public class LauncherStateTransitionAnimation {
* Creates and starts a new animation to a particular overlay view.
*/
@SuppressLint("NewApi")
- private void startAnimationToOverlay(final Workspace.State toWorkspaceState,
- final View buttonView, final View toView, final View contentView, final View revealView,
- final View overlaySearchBarView, final boolean animated, final boolean hideSearchBar,
- final PrivateTransitionCallbacks pCb) {
+ private AnimatorSet startAnimationToOverlay(final Workspace.State fromWorkspaceState,
+ final Workspace.State toWorkspaceState, final View buttonView, final View toView,
+ final View contentView, final View revealView, final View overlaySearchBarView,
+ final boolean animated, final PrivateTransitionCallbacks pCb) {
+ final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
final Resources res = mLauncher.getResources();
- final boolean material = Utilities.isLmpOrAbove();
+ final boolean material = Utilities.ATLEAST_LOLLIPOP;
final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
final int itemsAlphaStagger =
res.getInteger(R.integer.config_overlayItemsAlphaStagger);
@@ -230,11 +223,13 @@ public class LauncherStateTransitionAnimation {
// Create the workspace animation.
// NOTE: this call apparently also sets the state for the workspace if !animated
Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, -1,
- animated, overlaySearchBarView != null /* hasOverlaySearchBar */, layerViews);
+ animated, layerViews);
- if (animated && initialized) {
- mStateAnimation = LauncherAnimUtils.createAnimatorSet();
+ // Animate the search bar
+ startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState,
+ animated ? revealDuration : 0, overlaySearchBarView);
+ if (animated && initialized) {
// Setup the reveal view animation
int width = revealView.getMeasuredWidth();
int height = revealView.getMeasuredHeight();
@@ -274,7 +269,7 @@ public class LauncherStateTransitionAnimation {
// Play the animation
layerViews.put(revealView, BUILD_AND_SET_LAYER);
- mStateAnimation.play(panelAlphaAndDrift);
+ animation.play(panelAlphaAndDrift);
if (overlaySearchBarView != null) {
overlaySearchBarView.setAlpha(0f);
@@ -282,7 +277,7 @@ public class LauncherStateTransitionAnimation {
searchBarAlpha.setDuration(100);
searchBarAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER);
- mStateAnimation.play(searchBarAlpha);
+ animation.play(searchBarAlpha);
}
// Setup the animation for the content view
@@ -297,13 +292,13 @@ public class LauncherStateTransitionAnimation {
pageDrift.setDuration(revealDuration);
pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
pageDrift.setStartDelay(itemsAlphaStagger);
- mStateAnimation.play(pageDrift);
+ animation.play(pageDrift);
ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
itemsAlpha.setDuration(revealDuration);
itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
itemsAlpha.setStartDelay(itemsAlphaStagger);
- mStateAnimation.play(itemsAlpha);
+ animation.play(itemsAlpha);
if (material) {
float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
@@ -316,10 +311,10 @@ public class LauncherStateTransitionAnimation {
if (listener != null) {
reveal.addListener(listener);
}
- mStateAnimation.play(reveal);
+ animation.play(reveal);
}
- mStateAnimation.addListener(new AnimatorListenerAdapter() {
+ animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
dispatchOnLauncherTransitionEnd(fromView, animated, false);
@@ -335,12 +330,8 @@ public class LauncherStateTransitionAnimation {
}
}
- if (hideSearchBar) {
- mCb.onStateTransitionHideSearchBar();
- }
-
// This can hold unnecessary references to views.
- mStateAnimation = null;
+ cleanupAnimation();
pCb.onTransitionComplete();
}
@@ -348,7 +339,7 @@ public class LauncherStateTransitionAnimation {
// Play the workspace animation
if (workspaceAnim != null) {
- mStateAnimation.play(workspaceAnim);
+ animation.play(workspaceAnim);
}
// Dispatch the prepare transition signal
@@ -356,23 +347,22 @@ public class LauncherStateTransitionAnimation {
dispatchOnLauncherTransitionPrepare(toView, animated, false);
- final AnimatorSet stateAnimation = mStateAnimation;
+ final AnimatorSet stateAnimation = animation;
final Runnable startAnimRunnable = new Runnable() {
public void run() {
- // Check that mStateAnimation hasn't changed while
+ // Check that mCurrentAnimation hasn't changed while
// we waited for a layout/draw pass
- if (mStateAnimation != stateAnimation)
+ if (mCurrentAnimation != stateAnimation)
return;
dispatchOnLauncherTransitionStart(fromView, animated, false);
dispatchOnLauncherTransitionStart(toView, animated, false);
// Enable all necessary layers
- boolean isLmpOrAbove = Utilities.isLmpOrAbove();
for (View v : layerViews.keySet()) {
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
- if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
+ if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) {
v.buildLayer();
}
}
@@ -380,12 +370,14 @@ public class LauncherStateTransitionAnimation {
// Focus the new view
toView.requestFocus();
- mStateAnimation.start();
+ stateAnimation.start();
}
};
toView.bringToFront();
toView.setVisibility(View.VISIBLE);
toView.post(startAnimRunnable);
+
+ return animation;
} else {
toView.setTranslationX(0.0f);
toView.setTranslationY(0.0f);
@@ -397,10 +389,6 @@ public class LauncherStateTransitionAnimation {
// Show the content view
contentView.setVisibility(View.VISIBLE);
- if (hideSearchBar) {
- mCb.onStateTransitionHideSearchBar();
- }
-
dispatchOnLauncherTransitionPrepare(fromView, animated, false);
dispatchOnLauncherTransitionStart(fromView, animated, false);
dispatchOnLauncherTransitionEnd(fromView, animated, false);
@@ -408,18 +396,19 @@ public class LauncherStateTransitionAnimation {
dispatchOnLauncherTransitionStart(toView, animated, false);
dispatchOnLauncherTransitionEnd(toView, animated, false);
pCb.onTransitionComplete();
+
+ return null;
}
}
/**
* Starts and animation to the workspace from the apps view.
*/
- private void startAnimationToWorkspaceFromAllApps(final Workspace.State toWorkspaceState,
- final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) {
+ private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
+ final Workspace.State toWorkspaceState, final int toWorkspacePage,
+ final boolean animated, final Runnable onCompleteRunnable) {
AllAppsContainerView appsView = mLauncher.getAppsView();
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
- int[] mAllAppsToPanelDelta;
-
@Override
float getMaterialRevealViewFinalAlpha(View revealView) {
// No alpha anim from all apps
@@ -451,8 +440,8 @@ public class LauncherStateTransitionAnimation {
}
};
// Only animate the search bar if animating to spring loaded mode from all apps
- startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage,
- mLauncher.getAllAppsButton(), appsView, appsView.getContentView(),
+ mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
+ toWorkspacePage, mLauncher.getAllAppsButton(), appsView, appsView.getContentView(),
appsView.getRevealView(), appsView.getSearchBarView(), animated,
onCompleteRunnable, cb);
}
@@ -460,8 +449,9 @@ public class LauncherStateTransitionAnimation {
/**
* Starts and animation to the workspace from the widgets view.
*/
- private void startAnimationToWorkspaceFromWidgets(final Workspace.State toWorkspaceState,
- final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) {
+ private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
+ final Workspace.State toWorkspaceState, final int toWorkspacePage,
+ final boolean animated, final Runnable onCompleteRunnable) {
final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
@Override
@@ -479,21 +469,23 @@ public class LauncherStateTransitionAnimation {
};
}
};
- startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage,
- mLauncher.getWidgetsButton(), widgetsView, widgetsView.getContentView(),
- widgetsView.getRevealView(), null, animated, onCompleteRunnable, cb);
+ mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState,
+ toWorkspaceState, toWorkspacePage, mLauncher.getWidgetsButton(), widgetsView,
+ widgetsView.getContentView(), widgetsView.getRevealView(), null, animated,
+ onCompleteRunnable, cb);
}
/**
* Creates and starts a new animation to the workspace.
*/
- private void startAnimationToWorkspaceFromOverlay(final Workspace.State toWorkspaceState,
- final int toWorkspacePage, final View buttonView, final View fromView,
- final View contentView, final View revealView, final View overlaySearchBarView,
- final boolean animated, final Runnable onCompleteRunnable,
- final PrivateTransitionCallbacks pCb) {
+ private AnimatorSet startAnimationToWorkspaceFromOverlay(final Workspace.State fromWorkspaceState,
+ final Workspace.State toWorkspaceState, final int toWorkspacePage, final View buttonView,
+ final View fromView, final View contentView, final View revealView,
+ final View overlaySearchBarView, final boolean animated, final Runnable onCompleteRunnable,
+ final PrivateTransitionCallbacks pCb) {
+ final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
final Resources res = mLauncher.getResources();
- final boolean material = Utilities.isLmpOrAbove();
+ final boolean material = Utilities.ATLEAST_LOLLIPOP;
final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
final int itemsAlphaStagger =
res.getInteger(R.integer.config_overlayItemsAlphaStagger);
@@ -511,15 +503,16 @@ public class LauncherStateTransitionAnimation {
// Create the workspace animation.
// NOTE: this call apparently also sets the state for the workspace if !animated
Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
- toWorkspacePage, animated, overlaySearchBarView != null /* hasOverlaySearchBar */,
- layerViews);
+ toWorkspacePage, animated, layerViews);
- if (animated && initialized) {
- mStateAnimation = LauncherAnimUtils.createAnimatorSet();
+ // Animate the search bar
+ startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState,
+ animated ? revealDuration : 0, overlaySearchBarView);
+ if (animated && initialized) {
// Play the workspace animation
if (workspaceAnim != null) {
- mStateAnimation.play(workspaceAnim);
+ animation.play(workspaceAnim);
}
// hideAppsCustomizeHelper is called in some cases when it is already hidden
@@ -558,14 +551,14 @@ public class LauncherStateTransitionAnimation {
panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
panelDriftY.setInterpolator(decelerateInterpolator);
- mStateAnimation.play(panelDriftY);
+ animation.play(panelDriftY);
ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
0, revealViewToXDrift);
panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
panelDriftX.setInterpolator(decelerateInterpolator);
- mStateAnimation.play(panelDriftX);
+ animation.play(panelDriftX);
// Setup animation for the reveal panel alpha
final float revealViewToAlpha = !material ? 0f :
@@ -576,7 +569,7 @@ public class LauncherStateTransitionAnimation {
panelAlpha.setDuration(material ? revealDuration : 150);
panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
panelAlpha.setInterpolator(decelerateInterpolator);
- mStateAnimation.play(panelAlpha);
+ animation.play(panelAlpha);
}
// Setup the animation for the content view
@@ -589,13 +582,13 @@ public class LauncherStateTransitionAnimation {
pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
pageDrift.setInterpolator(decelerateInterpolator);
pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
- mStateAnimation.play(pageDrift);
+ animation.play(pageDrift);
contentView.setAlpha(1f);
ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
itemsAlpha.setDuration(100);
itemsAlpha.setInterpolator(decelerateInterpolator);
- mStateAnimation.play(itemsAlpha);
+ animation.play(itemsAlpha);
if (overlaySearchBarView != null) {
overlaySearchBarView.setAlpha(1f);
@@ -604,7 +597,7 @@ public class LauncherStateTransitionAnimation {
searchAlpha.setInterpolator(decelerateInterpolator);
searchAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER);
- mStateAnimation.play(searchAlpha);
+ animation.play(searchAlpha);
}
if (material) {
@@ -620,14 +613,14 @@ public class LauncherStateTransitionAnimation {
if (listener != null) {
reveal.addListener(listener);
}
- mStateAnimation.play(reveal);
+ animation.play(reveal);
}
dispatchOnLauncherTransitionPrepare(fromView, animated, true);
dispatchOnLauncherTransitionPrepare(toView, animated, true);
}
- mStateAnimation.addListener(new AnimatorListenerAdapter() {
+ animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
fromView.setVisibility(View.GONE);
@@ -657,35 +650,37 @@ public class LauncherStateTransitionAnimation {
}
// This can hold unnecessary references to views.
- mStateAnimation = null;
+ cleanupAnimation();
pCb.onTransitionComplete();
}
});
- final AnimatorSet stateAnimation = mStateAnimation;
+ final AnimatorSet stateAnimation = animation;
final Runnable startAnimRunnable = new Runnable() {
public void run() {
- // Check that mStateAnimation hasn't changed while
+ // Check that mCurrentAnimation hasn't changed while
// we waited for a layout/draw pass
- if (mStateAnimation != stateAnimation)
+ if (mCurrentAnimation != stateAnimation)
return;
+
dispatchOnLauncherTransitionStart(fromView, animated, false);
dispatchOnLauncherTransitionStart(toView, animated, false);
// Enable all necessary layers
- boolean isLmpOrAbove = Utilities.isLmpOrAbove();
for (View v : layerViews.keySet()) {
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
- if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
+ if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) {
v.buildLayer();
}
}
- mStateAnimation.start();
+ stateAnimation.start();
}
};
fromView.post(startAnimRunnable);
+
+ return animation;
} else {
fromView.setVisibility(View.GONE);
dispatchOnLauncherTransitionPrepare(fromView, animated, true);
@@ -700,9 +695,44 @@ public class LauncherStateTransitionAnimation {
if (onCompleteRunnable != null) {
onCompleteRunnable.run();
}
+
+ return null;
}
}
+ /**
+ * Coordinates the workspace search bar animation along with the launcher state animation.
+ */
+ private void startWorkspaceSearchBarAnimation(AnimatorSet animation,
+ final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, int duration,
+ View overlaySearchBar) {
+ final SearchDropTargetBar.State toSearchBarState =
+ toWorkspaceState.getSearchDropTargetBarState();
+
+ if (overlaySearchBar != null) {
+ if ((toWorkspaceState == Workspace.State.NORMAL) &&
+ (fromWorkspaceState == Workspace.State.NORMAL_HIDDEN)) {
+ // If we are transitioning from the overlay to the workspace, then show the
+ // workspace search bar immediately and let the overlay search bar fade out on top
+ mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, 0);
+ } else if (fromWorkspaceState == Workspace.State.NORMAL) {
+ // If we are transitioning from the workspace to the overlay, then keep the
+ // workspace search bar visible until the overlay search bar fades in on top
+ animation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, 0);
+ }
+ });
+ } else {
+ // Otherwise, then just animate the workspace search bar normally
+ mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, duration);
+ }
+ } else {
+ // If there is no overlay search bar, then just animate the workspace search bar
+ mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, duration);
+ }
+ }
/**
* Dispatches the prepare-transition event to suitable views.
@@ -753,10 +783,14 @@ public class LauncherStateTransitionAnimation {
* Cancels the current animation.
*/
private void cancelAnimation() {
- if (mStateAnimation != null) {
- mStateAnimation.setDuration(0);
- mStateAnimation.cancel();
- mStateAnimation = null;
+ if (mCurrentAnimation != null) {
+ mCurrentAnimation.setDuration(0);
+ mCurrentAnimation.cancel();
+ mCurrentAnimation = null;
}
}
+
+ @Thunk void cleanupAnimation() {
+ mCurrentAnimation = null;
+ }
}
diff --git a/src/com/android/launcher3/LauncherViewPropertyAnimator.java b/src/com/android/launcher3/LauncherViewPropertyAnimator.java
index 4cafbbfa6..4406a2c5c 100644
--- a/src/com/android/launcher3/LauncherViewPropertyAnimator.java
+++ b/src/com/android/launcher3/LauncherViewPropertyAnimator.java
@@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.EnumSet;
public class LauncherViewPropertyAnimator extends Animator implements AnimatorListener {
+
enum Properties {
TRANSLATION_X,
TRANSLATION_Y,
@@ -51,13 +52,12 @@ public class LauncherViewPropertyAnimator extends Animator implements AnimatorLi
long mStartDelay;
long mDuration;
TimeInterpolator mInterpolator;
- ArrayList<Animator.AnimatorListener> mListeners;
+ ArrayList<Animator.AnimatorListener> mListeners = new ArrayList<>();
boolean mRunning = false;
FirstFrameAnimatorHelper mFirstFrameHelper;
public LauncherViewPropertyAnimator(View target) {
mTarget = target;
- mListeners = new ArrayList<Animator.AnimatorListener>();
}
@Override
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 218c1a36f..05f0a0553 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -2293,7 +2293,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
// Besides disabling the accessibility long-click, this also prevents this view from getting
// accessibility focus.
info.setLongClickable(false);
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
}
}
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index 08f8e5601..40eadabd5 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -16,13 +16,17 @@
package com.android.launcher3;
+import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources.Theme;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Bundle;
import android.text.Layout;
import android.text.StaticLayout;
@@ -32,6 +36,8 @@ import android.view.View;
import android.view.View.OnClickListener;
public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implements OnClickListener {
+ private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
+ private static final float MIN_SATUNATION = 0.7f;
private static Theme sPreloaderTheme;
@@ -47,13 +53,14 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implemen
private Bitmap mIcon;
private Drawable mCenterDrawable;
- private Drawable mTopCornerDrawable;
+ private Drawable mSettingIconDrawable;
private boolean mDrawableSizeChanged;
private final TextPaint mPaint;
private Layout mSetupTextLayout;
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
boolean disabledForSafeMode) {
super(context);
@@ -70,6 +77,10 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implemen
mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
setBackgroundResource(R.drawable.quantum_panel_dark);
setWillNotDraw(false);
+
+ if (Utilities.ATLEAST_LOLLIPOP) {
+ setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
+ }
}
@Override
@@ -124,10 +135,12 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implemen
FastBitmapDrawable disabledIcon = mLauncher.createIconDrawable(mIcon);
disabledIcon.setGhostModeEnabled(true);
mCenterDrawable = disabledIcon;
- mTopCornerDrawable = null;
+ mSettingIconDrawable = null;
} else if (isReadyForClickSetup()) {
- mCenterDrawable = getResources().getDrawable(R.drawable.ic_setting);
- mTopCornerDrawable = new FastBitmapDrawable(mIcon);
+ mCenterDrawable = new FastBitmapDrawable(mIcon);
+ mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
+
+ updateSettingColor();
} else {
if (sPreloaderTheme == null) {
sPreloaderTheme = getResources().newTheme();
@@ -137,13 +150,25 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implemen
FastBitmapDrawable drawable = mLauncher.createIconDrawable(mIcon);
mCenterDrawable = new PreloadIconDrawable(drawable, sPreloaderTheme);
mCenterDrawable.setCallback(this);
- mTopCornerDrawable = null;
+ mSettingIconDrawable = null;
applyState();
}
mDrawableSizeChanged = true;
}
}
+ private void updateSettingColor() {
+ int color = Utilities.findDominantColorByHue(mIcon, 20);
+ // Make the dominant color bright.
+ float[] hsv = new float[3];
+ Color.colorToHSV(color, hsv);
+ hsv[1] = Math.min(hsv[1], MIN_SATUNATION);
+ hsv[2] = 1;
+ color = Color.HSVToColor(hsv);
+
+ mSettingIconDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+ }
+
@Override
protected boolean verifyDrawable(Drawable who) {
return (who == mCenterDrawable) || super.verifyDrawable(who);
@@ -169,6 +194,83 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implemen
&& (mInfo.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0;
}
+ private void updateDrawableBounds() {
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ int paddingTop = getPaddingTop();
+ int paddingBottom = getPaddingBottom();
+ int paddingLeft = getPaddingLeft();
+ int paddingRight = getPaddingRight();
+
+ int minPadding = getResources()
+ .getDimensionPixelSize(R.dimen.pending_widget_min_padding);
+
+ int availableWidth = getWidth() - paddingLeft - paddingRight - 2 * minPadding;
+ int availableHeight = getHeight() - paddingTop - paddingBottom - 2 * minPadding;
+
+ if (mSettingIconDrawable == null) {
+ int outset = (mCenterDrawable instanceof PreloadIconDrawable) ?
+ ((PreloadIconDrawable) mCenterDrawable).getOutset() : 0;
+ int maxSize = grid.iconSizePx + 2 * outset;
+ int size = Math.min(maxSize, Math.min(availableWidth, availableHeight));
+
+ mRect.set(0, 0, size, size);
+ mRect.inset(outset, outset);
+ mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
+ mCenterDrawable.setBounds(mRect);
+ } else {
+ float iconSize = Math.min(availableWidth, availableHeight);
+
+ // Use twice the setting size factor, as the setting is drawn at a corner and the
+ // icon is drawn in the center.
+ float settingIconScaleFactor = 1 + SETUP_ICON_SIZE_FACTOR * 2;
+ int maxSize = Math.max(availableWidth, availableHeight);
+ if (iconSize * settingIconScaleFactor > maxSize) {
+ // There is an overlap
+ iconSize = maxSize / settingIconScaleFactor;
+ }
+
+ int actualIconSize = (int) Math.min(iconSize, grid.iconSizePx);
+
+ // Recreate the setup text.
+ mSetupTextLayout = new StaticLayout(
+ getResources().getText(R.string.gadget_setup_text), mPaint, availableWidth,
+ Layout.Alignment.ALIGN_CENTER, 1, 0, true);
+ int textHeight = mSetupTextLayout.getHeight();
+
+ // Extra icon size due to the setting icon
+ float minHeightWithText = textHeight + actualIconSize * settingIconScaleFactor
+ + grid.iconDrawablePaddingPx;
+
+ int iconTop;
+ if (minHeightWithText < availableHeight) {
+ // We can draw the text as well
+ iconTop = (getHeight() - textHeight -
+ grid.iconDrawablePaddingPx - actualIconSize) / 2;
+
+ } else {
+ // The text will not fit. Only draw the icons.
+ iconTop = (getHeight() - actualIconSize) / 2;
+ mSetupTextLayout = null;
+ }
+
+ mRect.set(0, 0, actualIconSize, actualIconSize);
+ mRect.offset((getWidth() - actualIconSize) / 2, iconTop);
+ mCenterDrawable.setBounds(mRect);
+
+ mRect.left = paddingLeft + minPadding;
+ mRect.right = mRect.left + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
+ mRect.top = paddingTop + minPadding;
+ mRect.bottom = mRect.top + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
+ mSettingIconDrawable.setBounds(mRect);
+
+ if (mSetupTextLayout != null) {
+ // Set up position for dragging the text
+ mRect.left = paddingLeft + minPadding;
+ mRect.top = mCenterDrawable.getBounds().bottom + grid.iconDrawablePaddingPx;
+ }
+ }
+ }
+
@Override
protected void onDraw(Canvas canvas) {
if (mCenterDrawable == null) {
@@ -176,81 +278,21 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implemen
return;
}
- DeviceProfile grid = mLauncher.getDeviceProfile();
- if (mTopCornerDrawable == null) {
- if (mDrawableSizeChanged) {
- int outset = (mCenterDrawable instanceof PreloadIconDrawable) ?
- ((PreloadIconDrawable) mCenterDrawable).getOutset() : 0;
- int maxSize = grid.iconSizePx + 2 * outset;
- int size = Math.min(maxSize, Math.min(
- getWidth() - getPaddingLeft() - getPaddingRight(),
- getHeight() - getPaddingTop() - getPaddingBottom()));
-
- mRect.set(0, 0, size, size);
- mRect.inset(outset, outset);
- mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
- mCenterDrawable.setBounds(mRect);
- mDrawableSizeChanged = false;
- }
- mCenterDrawable.draw(canvas);
- } else {
- // Draw the top corner icon and "Setup" text is possible
- if (mDrawableSizeChanged) {
- int iconSize = grid.iconSizePx;
- int paddingTop = getPaddingTop();
- int paddingBottom = getPaddingBottom();
- int paddingLeft = getPaddingLeft();
- int paddingRight = getPaddingRight();
-
- int availableWidth = getWidth() - paddingLeft - paddingRight;
- int availableHeight = getHeight() - paddingTop - paddingBottom;
-
- // Recreate the setup text.
- mSetupTextLayout = new StaticLayout(
- getResources().getText(R.string.gadget_setup_text), mPaint, availableWidth,
- Layout.Alignment.ALIGN_CENTER, 1, 0, true);
- if (mSetupTextLayout.getLineCount() == 1) {
- // The text fits in a single line. No need to draw the setup icon.
- int size = Math.min(iconSize, Math.min(availableWidth,
- availableHeight - mSetupTextLayout.getHeight()));
- mRect.set(0, 0, size, size);
- mRect.offsetTo((getWidth() - mRect.width()) / 2,
- (getHeight() - mRect.height() - mSetupTextLayout.getHeight()
- - grid.iconDrawablePaddingPx) / 2);
-
- mTopCornerDrawable.setBounds(mRect);
-
- // Update left and top to indicate the position where the text will be drawn.
- mRect.left = paddingLeft;
- mRect.top = mRect.bottom + grid.iconDrawablePaddingPx;
- } else {
- // The text can't be drawn in a single line. Draw a setup icon instead.
- mSetupTextLayout = null;
- int size = Math.min(iconSize, Math.min(
- getWidth() - paddingLeft - paddingRight,
- getHeight() - paddingTop - paddingBottom));
- mRect.set(0, 0, size, size);
- mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
- mCenterDrawable.setBounds(mRect);
-
- size = Math.min(size / 2,
- Math.max(mRect.top - paddingTop, mRect.left - paddingLeft));
- mTopCornerDrawable.setBounds(paddingLeft, paddingTop,
- paddingLeft + size, paddingTop + size);
- }
- mDrawableSizeChanged = false;
- }
+ if (mDrawableSizeChanged) {
+ updateDrawableBounds();
+ mDrawableSizeChanged = false;
+ }
- if (mSetupTextLayout == null) {
- mCenterDrawable.draw(canvas);
- mTopCornerDrawable.draw(canvas);
- } else {
- canvas.save();
- canvas.translate(mRect.left, mRect.top);
- mSetupTextLayout.draw(canvas);
- canvas.restore();
- mTopCornerDrawable.draw(canvas);
- }
+ mCenterDrawable.draw(canvas);
+ if (mSettingIconDrawable != null) {
+ mSettingIconDrawable.draw(canvas);
}
+ if (mSetupTextLayout != null) {
+ canvas.save();
+ canvas.translate(mRect.left, mRect.top);
+ mSetupTextLayout.draw(canvas);
+ canvas.restore();
+ }
+
}
}
diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java
index bcb59c448..45e4b2c4a 100644
--- a/src/com/android/launcher3/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/PreloadIconDrawable.java
@@ -213,6 +213,10 @@ class PreloadIconDrawable extends Drawable {
return mAnimationProgress;
}
+ public boolean hasNotCompleted() {
+ return mAnimationProgress < ANIMATION_PROGRESS_COMPLETED;
+ }
+
@Override
public int getIntrinsicHeight() {
return mIcon.getIntrinsicHeight();
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index 4cdf1cac9..772a334b9 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -18,32 +18,57 @@ package com.android.launcher3;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
+import android.view.accessibility.AccessibilityManager;
import android.view.animation.AccelerateInterpolator;
import android.widget.FrameLayout;
+import com.android.launcher3.util.Thunk;
+
/*
* Ths bar will manage the transition between the QSB search bar and the delete drop
* targets so that each of the individual IconDropTargets don't have to.
*/
public class SearchDropTargetBar extends FrameLayout implements DragController.DragListener {
- private static final int TRANSITION_DURATION = 200;
+ /** The different states that the search bar space can be in. */
+ public enum State {
+ INVISIBLE (0f, 0f),
+ SEARCH_BAR (1f, 0f),
+ DROP_TARGET (0f, 1f);
+
+ private final float mSearchBarAlpha;
+ private final float mDropTargetBarAlpha;
+
+ State(float sbAlpha, float dtbAlpha) {
+ mSearchBarAlpha = sbAlpha;
+ mDropTargetBarAlpha = dtbAlpha;
+ }
+
+ float getSearchBarAlpha() {
+ return mSearchBarAlpha;
+ }
+
+ float getDropTargetBarAlpha() {
+ return mDropTargetBarAlpha;
+ }
+ }
- private ObjectAnimator mShowDropTargetBarAnim;
- private ValueAnimator mHideSearchBarAnim;
+ private static int DEFAULT_DRAG_FADE_DURATION = 175;
+
+ private LauncherViewPropertyAnimator mDropTargetBarAnimator;
+ private LauncherViewPropertyAnimator mQSBSearchBarAnimator;
private static final AccelerateInterpolator sAccelerateInterpolator =
new AccelerateInterpolator();
- private boolean mIsSearchBarHidden;
- private View mQSBSearchBar;
- private View mDropTargetBar;
+ private State mState = State.SEARCH_BAR;
+ @Thunk View mQSB;
+ @Thunk View mDropTargetBar;
private boolean mDeferOnDragEnd = false;
+ @Thunk boolean mAccessibilityEnabled = false;
// Drop targets
private ButtonDropTarget mInfoDropTarget;
@@ -75,39 +100,6 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
mUninstallDropTarget.setLauncher(launcher);
}
- public void setQsbSearchBar(View qsb) {
- mQSBSearchBar = qsb;
- if (mQSBSearchBar != null) {
- mHideSearchBarAnim = LauncherAnimUtils.ofFloat(mQSBSearchBar, "alpha", 1f, 0f);
- setupAnimation(mHideSearchBarAnim, mQSBSearchBar);
- } else {
- // Create a no-op animation of the search bar is null
- mHideSearchBarAnim = ValueAnimator.ofFloat(0, 0);
- mHideSearchBarAnim.setDuration(TRANSITION_DURATION);
- }
- }
-
- private void prepareStartAnimation(View v) {
- // Enable the hw layers before the animation starts (will be disabled in the onAnimationEnd
- // callback below)
- if (v != null) {
- v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- }
- }
-
- private void setupAnimation(ValueAnimator anim, final View v) {
- anim.setInterpolator(sAccelerateInterpolator);
- anim.setDuration(TRANSITION_DURATION);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (v != null) {
- v.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
- });
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -124,72 +116,89 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
// Create the various fade animations
mDropTargetBar.setAlpha(0f);
- mShowDropTargetBarAnim = LauncherAnimUtils.ofFloat(mDropTargetBar, "alpha", 0f, 1f);
- setupAnimation(mShowDropTargetBarAnim, mDropTargetBar);
- }
+ mDropTargetBarAnimator = new LauncherViewPropertyAnimator(mDropTargetBar);
+ mDropTargetBarAnimator.setInterpolator(sAccelerateInterpolator);
+ mDropTargetBarAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Ensure that the view is visible for the animation
+ mDropTargetBar.setVisibility(View.VISIBLE);
+ }
- /**
- * Finishes all the animations on the search and drop target bars.
- */
- public void finishAnimations() {
- prepareStartAnimation(mDropTargetBar);
- mShowDropTargetBarAnim.reverse();
- prepareStartAnimation(mQSBSearchBar);
- mHideSearchBarAnim.reverse();
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mDropTargetBar != null) {
+ AlphaUpdateListener.updateVisibility(mDropTargetBar, mAccessibilityEnabled);
+ }
+ }
+ });
}
- /**
- * Shows the search bar.
- */
- public void showSearchBar(boolean animated) {
- if (!mIsSearchBarHidden) return;
- if (animated) {
- prepareStartAnimation(mQSBSearchBar);
- mHideSearchBarAnim.reverse();
+ public void setQsbSearchBar(View qsb) {
+ mQSB = qsb;
+ if (mQSB != null) {
+ // Update the search ber animation
+ mQSBSearchBarAnimator = new LauncherViewPropertyAnimator(mQSB);
+ mQSBSearchBarAnimator.setInterpolator(sAccelerateInterpolator);
+ mQSBSearchBarAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Ensure that the view is visible for the animation
+ if (mQSB != null) {
+ mQSB.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mQSB != null) {
+ AlphaUpdateListener.updateVisibility(mQSB, mAccessibilityEnabled);
+ }
+ }
+ });
} else {
- mHideSearchBarAnim.cancel();
- if (mQSBSearchBar != null) {
- mQSBSearchBar.setAlpha(1f);
- }
+ mQSBSearchBarAnimator = null;
}
- mIsSearchBarHidden = false;
}
/**
- * Hides the search bar. We only use this for clings.
+ * Animates the current search bar state to a new state. If the {@param duration} is 0, then
+ * the state is applied immediately.
*/
- public void hideSearchBar(boolean animated) {
- if (mIsSearchBarHidden) return;
- if (animated) {
- prepareStartAnimation(mQSBSearchBar);
- mHideSearchBarAnim.start();
- } else {
- mHideSearchBarAnim.cancel();
- if (mQSBSearchBar != null) {
- mQSBSearchBar.setAlpha(0f);
- }
+ public void animateToState(State newState, int duration) {
+ if (mState != newState) {
+ mState = newState;
+
+ // Update the accessibility state
+ AccessibilityManager am = (AccessibilityManager)
+ getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ mAccessibilityEnabled = am.isEnabled();
+
+ animateViewAlpha(mQSBSearchBarAnimator, mQSB, newState.getSearchBarAlpha(),
+ duration);
+ animateViewAlpha(mDropTargetBarAnimator, mDropTargetBar, newState.getDropTargetBarAlpha(),
+ duration);
}
- mIsSearchBarHidden = true;
}
/**
- * Shows the drop target bar.
+ * Convenience method to animate the alpha of a view using hardware layers.
*/
- public void showDeleteTarget() {
- // Animate out the QSB search bar, and animate in the drop target bar
- prepareStartAnimation(mDropTargetBar);
- mShowDropTargetBarAnim.start();
- hideSearchBar(true);
- }
+ private void animateViewAlpha(LauncherViewPropertyAnimator animator, View v, float alpha,
+ int duration) {
+ if (v == null) {
+ return;
+ }
- /**
- * Hides the drop target bar.
- */
- public void hideDeleteTarget() {
- // Restore the QSB search bar, and animate out the drop target bar
- prepareStartAnimation(mDropTargetBar);
- mShowDropTargetBarAnim.reverse();
- showSearchBar(true);
+ animator.cancel();
+ if (Float.compare(v.getAlpha(), alpha) != 0) {
+ if (duration > 0) {
+ animator.alpha(alpha).withLayer().setDuration(duration).start();
+ } else {
+ v.setAlpha(alpha);
+ AlphaUpdateListener.updateVisibility(v, mAccessibilityEnabled);
+ }
+ }
}
/*
@@ -197,9 +206,13 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
*/
@Override
public void onDragStart(DragSource source, Object info, int dragAction) {
- showDeleteTarget();
+ animateToState(State.DROP_TARGET, DEFAULT_DRAG_FADE_DURATION);
}
+ /**
+ * This is called to defer hiding the delete drop target until the drop animation has completed,
+ * instead of hiding immediately when the drag has ended.
+ */
public void deferOnDragEnd() {
mDeferOnDragEnd = true;
}
@@ -207,22 +220,25 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
@Override
public void onDragEnd() {
if (!mDeferOnDragEnd) {
- hideDeleteTarget();
+ animateToState(State.SEARCH_BAR, DEFAULT_DRAG_FADE_DURATION);
} else {
mDeferOnDragEnd = false;
}
}
+ /**
+ * @return the bounds of the QSB search bar.
+ */
public Rect getSearchBarBounds() {
- if (mQSBSearchBar != null) {
+ if (mQSB != null) {
final int[] pos = new int[2];
- mQSBSearchBar.getLocationOnScreen(pos);
+ mQSB.getLocationOnScreen(pos);
final Rect rect = new Rect();
rect.left = pos[0];
rect.top = pos[1];
- rect.right = pos[0] + mQSBSearchBar.getWidth();
- rect.bottom = pos[1] + mQSBSearchBar.getHeight();
+ rect.right = pos[0] + mQSB.getWidth();
+ rect.bottom = pos[1] + mQSB.getHeight();
return rect;
} else {
return null;
@@ -230,8 +246,8 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
}
public void enableAccessibleDrag(boolean enable) {
- if (mQSBSearchBar != null) {
- mQSBSearchBar.setVisibility(enable ? View.GONE : View.VISIBLE);
+ if (mQSB != null) {
+ mQSB.setVisibility(enable ? View.GONE : View.VISIBLE);
}
mInfoDropTarget.enableAccessibleDrag(enable);
mDeleteDropTarget.enableAccessibleDrag(enable);
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 56c0b9d2f..5766cf2f2 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -198,13 +198,17 @@ public class ShortcutInfo extends ItemInfo {
return mIcon;
}
- public void updateIcon(IconCache iconCache) {
+ public void updateIcon(IconCache iconCache, boolean useLowRes) {
if (itemType == Favorites.ITEM_TYPE_APPLICATION) {
iconCache.getTitleAndIcon(this, promisedIntent != null ? promisedIntent : intent, user,
- shouldUseLowResIcon());
+ useLowRes);
}
}
+ public void updateIcon(IconCache iconCache) {
+ updateIcon(iconCache, shouldUseLowResIcon());
+ }
+
@Override
void onAddToDatabase(Context context, ContentValues values) {
super.onAddToDatabase(context, values);
diff --git a/src/com/android/launcher3/StylusEventHelper.java b/src/com/android/launcher3/StylusEventHelper.java
index da46e6a54..e03273a6e 100644
--- a/src/com/android/launcher3/StylusEventHelper.java
+++ b/src/com/android/launcher3/StylusEventHelper.java
@@ -1,8 +1,5 @@
package com.android.launcher3;
-import com.android.launcher3.Utilities;
-
-import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
@@ -77,8 +74,9 @@ public class StylusEventHelper {
* @param event The event to check.
* @return Whether a stylus button press occurred.
*/
- public static boolean isStylusButtonPressed(MotionEvent event) {
+ private static boolean isStylusButtonPressed(MotionEvent event) {
return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
- && event.isButtonPressed(MotionEvent.BUTTON_SECONDARY);
+ && ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY)
+ == MotionEvent.BUTTON_SECONDARY);
}
} \ No newline at end of file
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 0819f8ce0..955d4013c 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -38,7 +38,7 @@ public class UninstallDropTarget extends ButtonDropTarget {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public static boolean supportsDrop(Context context, Object info) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ if (Utilities.ATLEAST_JB_MR2) {
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
Bundle restrictions = userManager.getUserRestrictions();
if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8fd298df7..adedd33b2 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -87,6 +87,24 @@ public final class Utilities {
private static final int[] sLoc0 = new int[2];
private static final int[] sLoc1 = new int[2];
+ // TODO: use Build.VERSION_CODES when available
+ public static final boolean ATLEAST_MARSHMALLOW = Build.VERSION.SDK_INT >= 23;
+
+ public static final boolean ATLEAST_LOLLIPOP_MR1 =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1;
+
+ public static final boolean ATLEAST_LOLLIPOP =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+
+ public static final boolean ATLEAST_KITKAT =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+
+ public static final boolean ATLEAST_JB_MR1 =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+ public static final boolean ATLEAST_JB_MR2 =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
+
// To turn on these properties, type
// adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
private static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
@@ -110,23 +128,6 @@ public final class Utilities {
return sForceEnableRotation || context.getResources().getBoolean(R.bool.allow_rotation);
}
- /**
- * Indicates if the device is running LMP or higher.
- */
- public static boolean isLmpOrAbove() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
- }
-
- public static boolean isLmpMR1OrAbove() {
- // TODO(adamcohen): update to Build.VERSION_CODES.LOLLIPOP_MR1 once building against 22;
- return Build.VERSION.SDK_INT >= 22;
- }
-
- public static boolean isLmpMR1() {
- // TODO(adamcohen): update to Build.VERSION_CODES.LOLLIPOP_MR1 once building against 22;
- return Build.VERSION.SDK_INT == 22;
- }
-
public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
byte[] data = c.getBlob(iconIndex);
try {
@@ -517,7 +518,7 @@ public final class Utilities {
@TargetApi(Build.VERSION_CODES.KITKAT)
public static boolean isViewAttachedToWindow(View v) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ if (ATLEAST_KITKAT) {
return v.isAttachedToWindow();
} else {
// A proxy call which returns null, if the view is not attached to the window.
@@ -544,7 +545,7 @@ public final class Utilities {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
if (info.provider.getPackageName().equals(providerPkg)) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (ATLEAST_JB_MR1) {
if ((info.widgetCategory & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
return info;
} else if (defaultWidgetForSearchPackage == null) {
@@ -655,7 +656,7 @@ public final class Utilities {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static boolean isRtl(Resources res) {
- return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) &&
+ return ATLEAST_JB_MR1 &&
(res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
}
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 629387ed0..346055566 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -105,7 +105,7 @@ public class WidgetPreviewLoader {
* sizes (landscape vs portrait).
*/
private static class CacheDb extends SQLiteOpenHelper {
- private static final int DB_VERSION = 3;
+ private static final int DB_VERSION = 4;
private static final String TABLE_NAME = "shortcut_and_widget_previews";
private static final String COLUMN_COMPONENT = "componentName";
@@ -212,7 +212,6 @@ public class WidgetPreviewLoader {
public void removeObsoletePreviews(ArrayList<Object> list) {
Utilities.assertWorkerThread();
- LongSparseArray<UserHandleCompat> userIdCache = new LongSparseArray<>();
LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
for (Object obj : list) {
@@ -227,15 +226,7 @@ public class WidgetPreviewLoader {
pkg = info.provider.getPackageName();
}
- int userIdIndex = userIdCache.indexOfValue(user);
- final long userId;
- if (userIdIndex < 0) {
- userId = mUserManager.getSerialNumberForUser(user);
- userIdCache.put(userId, user);
- } else {
- userId = userIdCache.keyAt(userIdIndex);
- }
-
+ final long userId = mUserManager.getSerialNumberForUser(user);
HashSet<String> packages = validPackages.get(userId);
if (packages == null) {
packages = new HashSet<>();
@@ -361,8 +352,8 @@ public class WidgetPreviewLoader {
}
final boolean widgetPreviewExists = (drawable != null);
- final int spanX = info.getSpanX(launcher) < 1 ? 1 : info.getSpanX(launcher);
- final int spanY = info.getSpanY(launcher) < 1 ? 1 : info.getSpanY(launcher);
+ final int spanX = info.spanX;
+ final int spanY = info.spanY;
int previewWidth;
int previewHeight;
@@ -386,7 +377,7 @@ public class WidgetPreviewLoader {
preScaledWidthOut[0] = previewWidth;
}
if (previewWidth > maxPreviewWidth) {
- scale = maxPreviewWidth / (float) previewWidth;
+ scale = (maxPreviewWidth - 2 * mProfileBadgeMargin) / (float) (previewWidth);
}
if (scale != 1f) {
previewWidth = (int) (scale * previewWidth);
@@ -405,7 +396,7 @@ public class WidgetPreviewLoader {
}
// Draw the scaled preview into the final bitmap
- int x = (preview.getWidth() - previewWidth - mProfileBadgeMargin) / 2;
+ int x = (preview.getWidth() - previewWidth) / 2;
if (widgetPreviewExists) {
drawable.setBounds(x, 0, x + previewWidth, previewHeight);
drawable.draw(c);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 4a6b90afe..856e3b88a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -178,7 +178,24 @@ public class Workspace extends PagedView
// State variable that indicates whether the pages are small (ie when you're
// in all apps or customize mode)
- enum State { NORMAL, NORMAL_HIDDEN, SPRING_LOADED, OVERVIEW, OVERVIEW_HIDDEN};
+ enum State {
+ NORMAL (SearchDropTargetBar.State.SEARCH_BAR),
+ NORMAL_HIDDEN (SearchDropTargetBar.State.INVISIBLE),
+ SPRING_LOADED (SearchDropTargetBar.State.DROP_TARGET),
+ OVERVIEW (SearchDropTargetBar.State.INVISIBLE),
+ OVERVIEW_HIDDEN (SearchDropTargetBar.State.INVISIBLE);
+
+ private final SearchDropTargetBar.State mBarState;
+
+ State(SearchDropTargetBar.State searchBarState) {
+ mBarState = searchBarState;
+ }
+
+ public SearchDropTargetBar.State getSearchDropTargetBarState() {
+ return mBarState;
+ }
+ };
+
private State mState = State.NORMAL;
private boolean mIsSwitchingState = false;
@@ -306,8 +323,9 @@ public class Workspace extends PagedView
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.Workspace, defStyle, 0);
mSpringLoadedShrinkFactor =
- res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
- mOverviewModeShrinkFactor = grid.getOverviewModeScale(mIsRtl);
+ res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
+ mOverviewModeShrinkFactor =
+ res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
a.recycle();
@@ -869,6 +887,9 @@ public class Workspace extends PagedView
}
}
+ LauncherAccessibilityDelegate delegate =
+ LauncherAppState.getInstance().getAccessibilityDelegate();
+
// We enforce at least one page to add new items to. In the case that we remove the last
// such screen, we convert the last screen to the empty screen
int minScreens = 1 + numCustomPages();
@@ -883,6 +904,11 @@ public class Workspace extends PagedView
if (indexOfChild(cl) < currentPage) {
pageShift++;
}
+
+ if (delegate != null && delegate.isInAccessibleDrag()) {
+ cl.enableAccessibleDrag(false, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
+ }
+
removeView(cl);
} else {
// if this is the last non-custom content screen, convert it to the empty screen
@@ -1566,7 +1592,7 @@ public class Workspace extends PagedView
// Reset our click listener
setOnClickListener(mLauncher);
}
- mLauncher.getSearchBar().enableAccessibleDrag(enable);
+ mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable);
mLauncher.getHotseat().getLayout()
.enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
}
@@ -1952,15 +1978,17 @@ public class Workspace extends PagedView
int getOverviewModeTranslationY() {
DeviceProfile grid = mLauncher.getDeviceProfile();
- Rect overviewBar = grid.getOverviewModeButtonBarRect();
+ Rect workspacePadding = grid.getWorkspacePadding(Utilities.isRtl(getResources()));
+ int overviewButtonBarHeight = grid.getOverviewModeButtonBarHeight();
- int availableHeight = getViewportHeight();
int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
- int offsetFromTopEdge = (availableHeight - scaledHeight) / 2;
- int offsetToCenterInOverview = (availableHeight - mInsets.top - overviewBar.height()
- - scaledHeight) / 2;
-
- return -offsetFromTopEdge + mInsets.top + offsetToCenterInOverview;
+ int workspaceTop = mInsets.top + workspacePadding.top;
+ int workspaceBottom = getViewportHeight() - mInsets.bottom - workspacePadding.bottom;
+ int overviewTop = mInsets.top;
+ int overviewBottom = getViewportHeight() - mInsets.bottom - overviewButtonBarHeight;
+ int workspaceOffsetTopEdge = workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2;
+ int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2;
+ return -workspaceOffsetTopEdge + overviewOffsetTopEdge;
}
/**
@@ -1968,10 +1996,10 @@ public class Workspace extends PagedView
* to that new state.
*/
public Animator setStateWithAnimation(State toState, int toPage, boolean animated,
- boolean hasOverlaySearchBar, HashMap<View, Integer> layerViews) {
+ HashMap<View, Integer> layerViews) {
// Create the animation to the new state
Animator workspaceAnim = mStateTransitionAnimation.getAnimationToState(mState,
- toState, toPage, animated, hasOverlaySearchBar, layerViews);
+ toState, toPage, animated, layerViews);
// Update the current state
mState = toState;
@@ -1985,7 +2013,7 @@ public class Workspace extends PagedView
}
public void updateAccessibilityFlags() {
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
int total = getPageCount();
for (int i = numCustomPages(); i < total; i++) {
updateAccessibilityFlags((CellLayout) getPageAt(i), i);
@@ -2273,6 +2301,8 @@ public class Workspace extends PagedView
dragRect = new Rect(left, top, right, bottom);
} else if (child instanceof FolderIcon) {
int previewSize = grid.folderIconSizePx;
+ dragVisualizeOffset = new Point(-padding.get() / 2,
+ padding.get() / 2 - child.getPaddingTop());
dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
}
@@ -2289,14 +2319,14 @@ public class Workspace extends PagedView
throw new IllegalStateException(msg);
}
- DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
- DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible);
- dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
-
if (child.getParent() instanceof ShortcutAndWidgetContainer) {
mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
}
+ DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
+ DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible);
+ dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
+
b.recycle();
}
@@ -3763,7 +3793,11 @@ public class Workspace extends PagedView
if (parentCell != null) {
parentCell.removeView(v);
} else if (LauncherAppState.isDogfoodBuild()) {
- throw new NullPointerException("mDragInfo.cell has null parent");
+ // When an app is uninstalled using the drop target, we wait until resume to remove
+ // the icon. We also remove all the corresponding items from the workspace at
+ // {@link Launcher#bindComponentsRemoved}. That call can come before or after
+ // {@link Launcher#mOnResumeCallbacks} depending on how busy the worker thread is.
+ Log.e(TAG, "mDragInfo.cell has null parent");
}
if (v instanceof DropTarget) {
mDragController.removeDropTarget((DropTarget) v);
@@ -4050,6 +4084,16 @@ public class Workspace extends PagedView
});
}
+ public View getHomescreenIconByItemId(final long id) {
+ return getFirstMatch(new ItemOperator() {
+
+ @Override
+ public boolean evaluate(ItemInfo info, View v, View parent) {
+ return info != null && info.id == id;
+ }
+ });
+ }
+
public View getViewForTag(final Object tag) {
return getFirstMatch(new ItemOperator() {
@@ -4298,8 +4342,9 @@ public class Workspace extends PagedView
updates.contains(info)) {
ShortcutInfo si = (ShortcutInfo) info;
BubbleTextView shortcut = (BubbleTextView) v;
- boolean oldPromiseState = getTextViewIcon(shortcut)
- instanceof PreloadIconDrawable;
+ Drawable oldIcon = getTextViewIcon(shortcut);
+ boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
+ && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
shortcut.applyFromShortcutInfo(si, mIconCache,
si.isPromise() != oldPromiseState);
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index b8916a72b..54f63bbd8 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -211,22 +211,20 @@ public class WorkspaceStateTransitionAnimation {
mOverlayTransitionTime = res.getInteger(R.integer.config_overlayTransitionTime);
mSpringLoadedShrinkFactor =
res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100f;
+ mOverviewModeShrinkFactor =
+ res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha) / 100f;
- mOverviewModeShrinkFactor = grid.getOverviewModeScale(Utilities.isRtl(res));
mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
}
public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
- int toPage, boolean animated, boolean hasOverlaySearchBar,
- HashMap<View, Integer> layerViews) {
+ int toPage, boolean animated, HashMap<View, Integer> layerViews) {
AccessibilityManager am = (AccessibilityManager)
mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
final boolean accessibilityEnabled = am.isEnabled();
TransitionStates states = new TransitionStates(fromState, toState);
- int duration = getAnimationDuration(states);
- animateWorkspace(states, toPage, animated, duration, layerViews,
- accessibilityEnabled);
- animateSearchBar(states, animated, duration, hasOverlaySearchBar, layerViews,
+ int workspaceDuration = getAnimationDuration(states);
+ animateWorkspace(states, toPage, animated, workspaceDuration, layerViews,
accessibilityEnabled);
animateBackgroundGradient(states, animated, BACKGROUND_FADE_OUT_DURATION);
return mStateAnimator;
@@ -473,75 +471,9 @@ public class WorkspaceStateTransitionAnimation {
}
/**
- * Coordinates with the workspace animation to animate the search bar.
- *
- * TODO: This should really be coordinated with the SearchDropTargetBar, otherwise the
- * bar has no idea that it is hidden, and this has no idea what state the bar is
- * actually in.
- */
- private void animateSearchBar(TransitionStates states, boolean animated, int duration,
- boolean hasOverlaySearchBar, final HashMap<View, Integer> layerViews,
- final boolean accessibilityEnabled) {
-
- // The search bar is only visible in the workspace
- final View searchBar = mLauncher.getOrCreateQsbBar();
- if (searchBar != null) {
- final boolean searchBarWillBeShown = states.stateIsNormal;
- final float finalSearchBarAlpha = searchBarWillBeShown ? 1f : 0f;
- if (animated) {
- if (hasOverlaySearchBar) {
- // If there is an overlay search bar, then we will coordinate with it.
- mStateAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- // If we are transitioning to a visible search bar, show it immediately
- // and let the overlay search bar has faded out
- if (searchBarWillBeShown) {
- searchBar.setAlpha(finalSearchBarAlpha);
- AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
- }
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- // If we are transitioning to a hidden search bar, hide it only after
- // the overlay search bar has faded in
- if (!searchBarWillBeShown) {
- searchBar.setAlpha(finalSearchBarAlpha);
- AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
- }
- }
- });
- } else {
- // Otherwise, we can just do the normal animation
- LauncherViewPropertyAnimator searchBarAlpha =
- new LauncherViewPropertyAnimator(searchBar).alpha(finalSearchBarAlpha);
- searchBarAlpha.addListener(new AlphaUpdateListener(searchBar,
- accessibilityEnabled));
- searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- if (layerViews != null) {
- // If layerViews is not null, we add these views, and indicate that
- // the caller can manage layer state.
- layerViews.put(searchBar, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
- } else {
- // Otherwise let the animator handle layer management.
- searchBarAlpha.withLayer();
- }
- searchBarAlpha.setDuration(duration);
- mStateAnimator.play(searchBarAlpha);
- }
- } else {
- // Set the search bar state immediately
- searchBar.setAlpha(finalSearchBarAlpha);
- AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
- }
- }
- }
-
- /**
* Animates the background scrim. Add to the state animator to prevent jankiness.
*
- * @param finalAlpha the final alpha for the background scrim
+ * @param states the current and final workspace states
* @param animated whether or not to set the background alpha immediately
* @duration duration of the animation
*/
diff --git a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
new file mode 100644
index 000000000..117aca921
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2015 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.launcher3.allapps;
+
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import android.view.Gravity;
+import com.android.launcher3.R;
+
+/**
+ * A helper class to positon and orient a drawable to be drawn.
+ */
+class TransformedImageDrawable {
+ private Drawable mImage;
+ private float mXPercent;
+ private float mYPercent;
+ private int mGravity;
+
+ /**
+ * @param gravity If one of the Gravity center values, the x and y offset will take the width
+ * and height of the image into account to center the image to the offset.
+ */
+ public TransformedImageDrawable(Resources res, int resourceId, float xPct, float yPct,
+ int gravity) {
+ mImage = res.getDrawable(resourceId);
+ mXPercent = xPct;
+ mYPercent = yPct;
+ mGravity = gravity;
+ }
+
+ public void setAlpha(int alpha) {
+ mImage.setAlpha(alpha);
+ }
+
+ public int getAlpha() {
+ return mImage.getAlpha();
+ }
+
+ public void updateBounds(Rect bounds) {
+ int width = mImage.getIntrinsicWidth();
+ int height = mImage.getIntrinsicHeight();
+ int left = bounds.left + (int) (mXPercent * bounds.width());
+ int top = bounds.top + (int) (mYPercent * bounds.height());
+ if ((mGravity & Gravity.CENTER_HORIZONTAL) == Gravity.CENTER_HORIZONTAL) {
+ left -= (width / 2);
+ }
+ if ((mGravity & Gravity.CENTER_VERTICAL) == Gravity.CENTER_VERTICAL) {
+ top -= (height / 2);
+ }
+ mImage.setBounds(left, top, left + width, top + height);
+ }
+
+ public void draw(Canvas canvas) {
+ int c = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ mImage.draw(canvas);
+ canvas.restoreToCount(c);
+ }
+}
+
+/**
+ * This is a custom composite drawable that has a fixed virtual size and dynamically lays out its
+ * children images relatively within its bounds. This way, we can reduce the memory usage of a
+ * single, large sparsely populated image.
+ */
+public class AllAppsBackgroundDrawable extends Drawable {
+
+ private final TransformedImageDrawable mHand;
+ private final TransformedImageDrawable[] mIcons;
+ private final int mWidth;
+ private final int mHeight;
+
+ private ObjectAnimator mBackgroundAnim;
+
+ public AllAppsBackgroundDrawable(Context context) {
+ Resources res = context.getResources();
+ mHand = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_hand,
+ 0.575f, 0.1f, Gravity.CENTER_HORIZONTAL);
+ mIcons = new TransformedImageDrawable[4];
+ mIcons[0] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_1,
+ 0.375f, 0, Gravity.CENTER_HORIZONTAL);
+ mIcons[1] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_2,
+ 0.3125f, 0.25f, Gravity.CENTER_HORIZONTAL);
+ mIcons[2] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_3,
+ 0.475f, 0.4f, Gravity.CENTER_HORIZONTAL);
+ mIcons[3] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_4,
+ 0.7f, 0.125f, Gravity.CENTER_HORIZONTAL);
+ mWidth = res.getDimensionPixelSize(R.dimen.all_apps_background_canvas_width);
+ mHeight = res.getDimensionPixelSize(R.dimen.all_apps_background_canvas_height);
+ }
+
+ /**
+ * Animates the background alpha.
+ */
+ public void animateBgAlpha(float finalAlpha, int duration) {
+ int finalAlphaI = (int) (finalAlpha * 255f);
+ if (getAlpha() != finalAlphaI) {
+ mBackgroundAnim = cancelAnimator(mBackgroundAnim);
+ mBackgroundAnim = ObjectAnimator.ofInt(this, "alpha", finalAlphaI);
+ mBackgroundAnim.setDuration(duration);
+ mBackgroundAnim.start();
+ }
+ }
+
+ /**
+ * Sets the background alpha immediately.
+ */
+ public void setBgAlpha(float finalAlpha) {
+ int finalAlphaI = (int) (finalAlpha * 255f);
+ if (getAlpha() != finalAlphaI) {
+ mBackgroundAnim = cancelAnimator(mBackgroundAnim);
+ setAlpha(finalAlphaI);
+ }
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ mHand.draw(canvas);
+ for (int i = 0; i < mIcons.length; i++) {
+ mIcons[i].draw(canvas);
+ }
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mHand.updateBounds(bounds);
+ for (int i = 0; i < mIcons.length; i++) {
+ mIcons[i].updateBounds(bounds);
+ }
+ invalidateSelf();
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mHand.setAlpha(alpha);
+ for (int i = 0; i < mIcons.length; i++) {
+ mIcons[i].setAlpha(alpha);
+ }
+ invalidateSelf();
+ }
+
+ @Override
+ public int getAlpha() {
+ return mHand.getAlpha();
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ // Do nothing
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ private ObjectAnimator cancelAnimator(ObjectAnimator animator) {
+ if (animator != null) {
+ animator.removeAllListeners();
+ animator.cancel();
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 67d572819..88c6acada 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -16,34 +16,26 @@
package com.android.launcher3.allapps;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.InsetDrawable;
-import android.os.Build;
-import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.FrameLayout;
import android.widget.LinearLayout;
-
import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseContainerView;
-import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
-import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
@@ -53,7 +45,6 @@ import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherTransitionable;
import com.android.launcher3.R;
-import com.android.launcher3.Stats;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.util.ComponentKey;
@@ -155,6 +146,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
@Thunk AllAppsSearchBarController mSearchBarController;
private ViewGroup mSearchBarContainerView;
private View mSearchBarView;
+ private SpannableStringBuilder mSearchQueryBuilder = null;
private int mSectionNamesMargin;
private int mNumAppsPerRow;
@@ -165,7 +157,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
// This coordinate is relative to its parent
private final Point mIconLastTouchPos = new Point();
- private SpannableStringBuilder mSearchQueryBuilder = null;
+ private View.OnClickListener mSearchClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent searchIntent = (Intent) v.getTag();
+ mLauncher.startActivitySafely(v, searchIntent, null);
+ }
+ };
public AllAppsContainerView(Context context) {
this(context, null);
@@ -182,8 +180,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
mLauncher = (Launcher) context;
mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
mApps = new AlphabeticalAppsList(context);
- mAdapter = new AllAppsGridAdapter(context, mApps, this, mLauncher, this);
- mAdapter.setEmptySearchText(res.getString(R.string.all_apps_loading_message));
+ mAdapter = new AllAppsGridAdapter(mLauncher, mApps, this, mLauncher, this);
mApps.setAdapter(mAdapter);
mLayoutManager = mAdapter.getLayoutManager();
mItemDecoration = mAdapter.getItemDecoration();
@@ -528,7 +525,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
ItemInfo itemInfo = (ItemInfo) d.dragInfo;
if (layout != null) {
- layout.calculateSpans(itemInfo);
showOutOfSpaceMessage =
!layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
}
@@ -559,8 +555,9 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
@Override
public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
if (toWorkspace) {
- // Reset the search bar after transitioning home
+ // Reset the search bar and base recycler view after transitioning home
mSearchBarController.reset();
+ mAppsRecyclerView.reset();
}
}
@@ -616,19 +613,16 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
@Override
public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
if (apps != null) {
- if (apps.isEmpty()) {
- String formatStr = getResources().getString(R.string.all_apps_no_search_results);
- mAdapter.setEmptySearchText(String.format(formatStr, query));
- } else {
- mAppsRecyclerView.scrollToTop();
- }
mApps.setOrderedFilter(apps);
+ mAdapter.setLastSearchQuery(query);
+ mAppsRecyclerView.onSearchResultsChanged();
}
}
@Override
public void clearSearchResult() {
mApps.setOrderedFilter(null);
+ mAppsRecyclerView.onSearchResultsChanged();
// Clear the search query
mSearchQueryBuilder.clear();
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 057883cab..1f95133d4 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -16,21 +16,30 @@
package com.android.launcher3.allapps;
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.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.os.Handler;
+import android.graphics.drawable.Drawable;
+import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.net.Uri;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.Thunk;
@@ -42,7 +51,7 @@ import java.util.List;
/**
* The grid view adapter of all the apps.
*/
-class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
+public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
public static final String TAG = "AppsGridAdapter";
private static final boolean DEBUG = false;
@@ -55,6 +64,10 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
public static final int PREDICTION_ICON_VIEW_TYPE = 2;
// The message shown when there are no filtered results
public static final int EMPTY_SEARCH_VIEW_TYPE = 3;
+ // A divider that separates the apps list and the search market button
+ public static final int SEARCH_MARKET_DIVIDER_VIEW_TYPE = 4;
+ // The message to continue to a market search when there are no filtered results
+ public static final int SEARCH_MARKET_VIEW_TYPE = 5;
/**
* ViewHolder for each icon.
@@ -69,6 +82,38 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
}
/**
+ * A subclass of GridLayoutManager that overrides accessibility values during app search.
+ */
+ public class AppsGridLayoutManager extends GridLayoutManager {
+
+ public AppsGridLayoutManager(Context context) {
+ super(context, 1, GridLayoutManager.VERTICAL, false);
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+
+ // Ensure that we only report the number apps for accessibility not including other
+ // adapter views
+ final AccessibilityRecordCompat record = AccessibilityEventCompat
+ .asRecord(event);
+ record.setItemCount(mApps.getNumFilteredApps());
+ }
+
+ @Override
+ public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ if (mApps.hasNoFilteredResults()) {
+ // Disregard the no-search-results text as a list item for accessibility
+ return 0;
+ } else {
+ return super.getRowCountForAccessibility(recycler, state);
+ }
+ }
+ }
+
+ /**
* Helper class to size the grid items.
*/
public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup {
@@ -80,11 +125,6 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
@Override
public int getSpanSize(int position) {
- if (mApps.hasNoFilteredResults()) {
- // Empty view spans full width
- return mAppsPerRow;
- }
-
switch (mApps.getAdapterItems().get(position).viewType) {
case AllAppsGridAdapter.ICON_VIEW_TYPE:
case AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE:
@@ -279,6 +319,7 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
}
}
+ private Launcher mLauncher;
private LayoutInflater mLayoutInflater;
@Thunk AlphabeticalAppsList mApps;
private GridLayoutManager mGridLayoutMgr;
@@ -291,7 +332,19 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
@Thunk int mPredictionBarDividerOffset;
@Thunk int mAppsPerRow;
@Thunk boolean mIsRtl;
- private String mEmptySearchText;
+
+ // The text to show when there are no search results and no market search handler.
+ private String mEmptySearchMessage;
+ // The name of the market app which handles searches, to be used in the format str
+ // below when updating the search-market view. Only needs to be loaded once.
+ private String mMarketAppName;
+ // The text to show when there is a market app which can handle a specific query, updated
+ // each time the search query changes.
+ private String mMarketSearchMessage;
+ // The intent to send off to the market app, updated each time the search query changes.
+ private Intent mMarketSearchIntent;
+ // The last query that the user entered into the search field
+ private String mLastSearchQuery;
// Section drawing
@Thunk int mSectionNamesMargin;
@@ -299,16 +352,18 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
@Thunk Paint mSectionTextPaint;
@Thunk Paint mPredictedAppsDividerPaint;
- public AllAppsGridAdapter(Context context, AlphabeticalAppsList apps,
+ public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps,
View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
View.OnLongClickListener iconLongClickListener) {
- Resources res = context.getResources();
+ Resources res = launcher.getResources();
+ mLauncher = launcher;
mApps = apps;
+ mEmptySearchMessage = res.getString(R.string.all_apps_loading_message);
mGridSizer = new GridSpanSizer();
- mGridLayoutMgr = new GridLayoutManager(context, 1, GridLayoutManager.VERTICAL, false);
+ mGridLayoutMgr = new AppsGridLayoutManager(launcher);
mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
mItemDecoration = new GridItemDecoration();
- mLayoutInflater = LayoutInflater.from(context);
+ mLayoutInflater = LayoutInflater.from(launcher);
mTouchListener = touchListener;
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
@@ -328,6 +383,14 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
mPredictionBarDividerOffset =
(-res.getDimensionPixelSize(R.dimen.all_apps_prediction_icon_bottom_padding) +
res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding)) / 2;
+
+ // Resolve the market app handling additional searches
+ PackageManager pm = launcher.getPackageManager();
+ ResolveInfo marketInfo = pm.resolveActivity(createMarketSearchIntent(""),
+ PackageManager.MATCH_DEFAULT_ONLY);
+ if (marketInfo != null) {
+ mMarketAppName = marketInfo.loadLabel(pm).toString();
+ }
}
/**
@@ -346,10 +409,19 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
}
/**
- * Sets the text to show when there are no apps.
+ * Sets the last search query that was made, used to show when there are no results and to also
+ * seed the intent for searching the market.
*/
- public void setEmptySearchText(String query) {
- mEmptySearchText = query;
+ public void setLastSearchQuery(String query) {
+ Resources res = mLauncher.getResources();
+ String formatStr = res.getString(R.string.all_apps_no_search_results);
+ mLastSearchQuery = query;
+ mEmptySearchMessage = String.format(formatStr, query);
+ if (mMarketAppName != null) {
+ mMarketSearchMessage = String.format(res.getString(R.string.all_apps_search_market_message),
+ mMarketAppName);
+ mMarketSearchIntent = createMarketSearchIntent(query);
+ }
}
/**
@@ -378,9 +450,6 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
- case EMPTY_SEARCH_VIEW_TYPE:
- return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search, parent,
- false));
case SECTION_BREAK_VIEW_TYPE:
return new ViewHolder(new View(parent.getContext()));
case ICON_VIEW_TYPE: {
@@ -405,6 +474,22 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
icon.setFocusable(true);
return new ViewHolder(icon);
}
+ case EMPTY_SEARCH_VIEW_TYPE:
+ return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
+ parent, false));
+ case SEARCH_MARKET_DIVIDER_VIEW_TYPE:
+ return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_search_market_divider,
+ parent, false));
+ case SEARCH_MARKET_VIEW_TYPE:
+ View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
+ parent, false);
+ searchMarketView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mLauncher.startSearchFromAllApps(v, mMarketSearchIntent, mLastSearchQuery);
+ }
+ });
+ return new ViewHolder(searchMarketView);
default:
throw new RuntimeException("Unexpected view type");
}
@@ -426,28 +511,47 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
break;
}
case EMPTY_SEARCH_VIEW_TYPE:
- TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text);
- emptyViewText.setText(mEmptySearchText);
+ TextView emptyViewText = (TextView) holder.mContent;
+ emptyViewText.setText(mEmptySearchMessage);
+ emptyViewText.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
+ Gravity.START | Gravity.CENTER_VERTICAL);
+ break;
+ case SEARCH_MARKET_VIEW_TYPE:
+ TextView searchView = (TextView) holder.mContent;
+ if (mMarketSearchIntent != null) {
+ searchView.setVisibility(View.VISIBLE);
+ searchView.setContentDescription(mMarketSearchMessage);
+ searchView.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
+ Gravity.START | Gravity.CENTER_VERTICAL);
+ searchView.setText(mMarketSearchMessage);
+ } else {
+ searchView.setVisibility(View.GONE);
+ }
break;
}
}
@Override
public int getItemCount() {
- if (mApps.hasNoFilteredResults()) {
- // For the empty view
- return 1;
- }
return mApps.getAdapterItems().size();
}
@Override
public int getItemViewType(int position) {
- if (mApps.hasNoFilteredResults()) {
- return EMPTY_SEARCH_VIEW_TYPE;
- }
-
AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
return item.viewType;
}
+
+ /**
+ * Creates a new market search intent.
+ */
+ private Intent createMarketSearchIntent(String query) {
+ Uri marketSearchUri = Uri.parse("market://search")
+ .buildUpon()
+ .appendQueryParameter("q", query)
+ .build();
+ Intent marketSearchIntent = new Intent(Intent.ACTION_VIEW);
+ marketSearchIntent.setData(marketSearchUri);
+ return marketSearchIntent;
+ }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 730c8d15a..2f66e2cad 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,8 +15,11 @@
*/
package com.android.launcher3.allapps;
+import android.animation.ObjectAnimator;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -26,6 +29,7 @@ import android.view.View;
import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.BaseRecyclerViewFastScrollBar;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
import com.android.launcher3.Stats;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.Thunk;
@@ -57,6 +61,9 @@ public class AllAppsRecyclerView extends BaseRecyclerView
private ScrollPositionState mScrollPosState = new ScrollPositionState();
+ private AllAppsBackgroundDrawable mEmptySearchBackground;
+ private int mEmptySearchBackgroundTopOffset;
+
public AllAppsRecyclerView(Context context) {
this(context, null);
}
@@ -72,6 +79,11 @@ public class AllAppsRecyclerView extends BaseRecyclerView
public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr);
+
+ Resources res = getResources();
+ mScrollbar.setDetachThumbOnFastScroll();
+ mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
+ R.dimen.all_apps_empty_search_bg_top_offset);
}
/**
@@ -90,6 +102,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
RecyclerView.RecycledViewPool pool = getRecycledViewPool();
int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
pool.setMaxRecycledViews(AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE, 1);
+ pool.setMaxRecycledViews(AllAppsGridAdapter.SEARCH_MARKET_DIVIDER_VIEW_TYPE, 1);
+ pool.setMaxRecycledViews(AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE, 1);
pool.setMaxRecycledViews(AllAppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow);
pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE, mNumAppsPerRow);
pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
@@ -99,6 +113,10 @@ public class AllAppsRecyclerView extends BaseRecyclerView
* Scrolls this recycler view to the top.
*/
public void scrollToTop() {
+ // Ensure we reattach the scrollbar if it was previously detached while fast-scrolling
+ if (mScrollbar.isThumbDetached()) {
+ mScrollbar.reattachThumbToScroll();
+ }
scrollToPosition(0);
}
@@ -115,6 +133,30 @@ public class AllAppsRecyclerView extends BaseRecyclerView
}
@Override
+ public void onDraw(Canvas c) {
+ // Draw the background
+ if (mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
+ c.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
+ getWidth() - mBackgroundPadding.right,
+ getHeight() - mBackgroundPadding.bottom);
+
+ mEmptySearchBackground.draw(c);
+ }
+
+ super.onDraw(c);
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return who == mEmptySearchBackground || super.verifyDrawable(who);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ updateEmptySearchBackgroundBounds();
+ }
+
+ @Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -134,6 +176,25 @@ public class AllAppsRecyclerView extends BaseRecyclerView
}
}
+ public void onSearchResultsChanged() {
+ // Always scroll the view to the top so the user can see the changed results
+ scrollToTop();
+
+ if (mApps.hasNoFilteredResults()) {
+ if (mEmptySearchBackground == null) {
+ mEmptySearchBackground = new AllAppsBackgroundDrawable(getContext());
+ mEmptySearchBackground.setAlpha(0);
+ mEmptySearchBackground.setCallback(this);
+ updateEmptySearchBackgroundBounds();
+ }
+ mEmptySearchBackground.animateBgAlpha(1f, 150);
+ } else if (mEmptySearchBackground != null) {
+ // For the time being, we just immediately hide the background to ensure that it does
+ // not overlap with the results
+ mEmptySearchBackground.setBgAlpha(0f);
+ }
+ }
+
/**
* Maps the touch (from 0..1) to the adapter position that should be visible.
*/
@@ -166,8 +227,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
}
// Map the touch position back to the scroll of the recycler view
- getCurScrollState(mScrollPosState, mApps.getAdapterItems());
- int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
+ getCurScrollState(mScrollPosState);
+ int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
@@ -214,24 +275,83 @@ public class AllAppsRecyclerView extends BaseRecyclerView
* Updates the bounds for the scrollbar.
*/
@Override
- public void onUpdateScrollbar() {
+ public void onUpdateScrollbar(int dy) {
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
// Skip early if there are no items or we haven't been measured
if (items.isEmpty() || mNumAppsPerRow == 0) {
- mScrollbar.setScrollbarThumbOffset(-1, -1);
+ mScrollbar.setThumbOffset(-1, -1);
return;
}
// Find the index and height of the first visible row (all rows have the same height)
int rowCount = mApps.getNumAppRows();
- getCurScrollState(mScrollPosState, items);
+ getCurScrollState(mScrollPosState);
if (mScrollPosState.rowIndex < 0) {
- mScrollbar.setScrollbarThumbOffset(-1, -1);
+ mScrollbar.setThumbOffset(-1, -1);
return;
}
- synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
+ // Only show the scrollbar if there is height to be scrolled
+ int availableScrollBarHeight = getAvailableScrollBarHeight();
+ int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows(), mScrollPosState.rowHeight);
+ if (availableScrollHeight <= 0) {
+ mScrollbar.setThumbOffset(-1, -1);
+ return;
+ }
+
+ // Calculate the current scroll position, the scrollY of the recycler view accounts for the
+ // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
+ // padding)
+ int scrollY = getPaddingTop() +
+ (mScrollPosState.rowIndex * mScrollPosState.rowHeight) - mScrollPosState.rowTopOffset;
+ int scrollBarY = mBackgroundPadding.top +
+ (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
+
+ if (mScrollbar.isThumbDetached()) {
+ int scrollBarX;
+ if (Utilities.isRtl(getResources())) {
+ scrollBarX = mBackgroundPadding.left;
+ } else {
+ scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
+ }
+
+ if (mScrollbar.isDraggingThumb()) {
+ // If the thumb is detached, then just update the thumb to the current
+ // touch position
+ mScrollbar.setThumbOffset(scrollBarX, (int) mScrollbar.getLastTouchY());
+ } else {
+ int thumbScrollY = mScrollbar.getThumbOffset().y;
+ int diffScrollY = scrollBarY - thumbScrollY;
+ if (diffScrollY * dy > 0f) {
+ // User is scrolling in the same direction the thumb needs to catch up to the
+ // current scroll position. We do this by mapping the difference in movement
+ // from the original scroll bar position to the difference in movement necessary
+ // in the detached thumb position to ensure that both speed towards the same
+ // position at either end of the list.
+ if (dy < 0) {
+ int offset = (int) ((dy * thumbScrollY) / (float) scrollBarY);
+ thumbScrollY += Math.max(offset, diffScrollY);
+ } else {
+ int offset = (int) ((dy * (availableScrollBarHeight - thumbScrollY)) /
+ (float) (availableScrollBarHeight - scrollBarY));
+ thumbScrollY += Math.min(offset, diffScrollY);
+ }
+ thumbScrollY = Math.max(0, Math.min(availableScrollBarHeight, thumbScrollY));
+ mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+ if (scrollBarY == thumbScrollY) {
+ mScrollbar.reattachThumbToScroll();
+ }
+ } else {
+ // User is scrolling in an opposite direction to the direction that the thumb
+ // needs to catch up to the scroll position. Do nothing except for updating
+ // the scroll bar x to match the thumb width.
+ mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+ }
+ }
+ } else {
+ synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
+ }
}
/**
@@ -283,13 +403,13 @@ public class AllAppsRecyclerView extends BaseRecyclerView
/**
* Returns the current scroll state of the apps rows.
*/
- private void getCurScrollState(ScrollPositionState stateOut,
- List<AlphabeticalAppsList.AdapterItem> items) {
+ protected void getCurScrollState(ScrollPositionState stateOut) {
stateOut.rowIndex = -1;
stateOut.rowTopOffset = -1;
stateOut.rowHeight = -1;
// Return early if there are no items or we haven't been measured
+ List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
if (items.isEmpty() || mNumAppsPerRow == 0) {
return;
}
@@ -324,4 +444,20 @@ public class AllAppsRecyclerView extends BaseRecyclerView
return 0;
}
}
+
+ /**
+ * Updates the bounds of the empty search background.
+ */
+ private void updateEmptySearchBackgroundBounds() {
+ if (mEmptySearchBackground == null) {
+ return;
+ }
+
+ // Center the empty search background on this new view bounds
+ int x = (getMeasuredWidth() - mEmptySearchBackground.getIntrinsicWidth()) / 2;
+ int y = mEmptySearchBackgroundTopOffset;
+ mEmptySearchBackground.setBounds(x, y,
+ x + mEmptySearchBackground.getIntrinsicWidth(),
+ y + mEmptySearchBackground.getIntrinsicHeight());
+ }
}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 47241ce5d..dac0df12a 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -43,6 +43,11 @@ public class AlphabeticalAppsList {
private static final boolean DEBUG = false;
private static final boolean DEBUG_PREDICTIONS = false;
+ private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION = 0;
+ private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS = 1;
+
+ private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
+
/**
* Info about a section in the alphabetic list
*/
@@ -81,8 +86,6 @@ public class AlphabeticalAppsList {
public int position;
// The type of this item
public int viewType;
- // The row that this item shows up on
- public int rowIndex;
/** Section & App properties */
// The section for this item
@@ -94,6 +97,8 @@ public class AlphabeticalAppsList {
public String sectionName = null;
// The index of this app in the section
public int sectionAppIndex = -1;
+ // The row that this item shows up on
+ public int rowIndex;
// The index of this app in the row
public int rowAppIndex;
// The associated AppInfo for the app
@@ -111,14 +116,14 @@ public class AlphabeticalAppsList {
}
public static AdapterItem asPredictedApp(int pos, SectionInfo section, String sectionName,
- int sectionAppIndex, AppInfo appInfo, int appIndex) {
+ int sectionAppIndex, AppInfo appInfo, int appIndex) {
AdapterItem item = asApp(pos, section, sectionName, sectionAppIndex, appInfo, appIndex);
item.viewType = AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE;
return item;
}
public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
- int sectionAppIndex, AppInfo appInfo, int appIndex) {
+ int sectionAppIndex, AppInfo appInfo, int appIndex) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.ICON_VIEW_TYPE;
item.position = pos;
@@ -129,6 +134,27 @@ public class AlphabeticalAppsList {
item.appIndex = appIndex;
return item;
}
+
+ public static AdapterItem asEmptySearch(int pos) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE;
+ item.position = pos;
+ return item;
+ }
+
+ public static AdapterItem asDivider(int pos) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = AllAppsGridAdapter.SEARCH_MARKET_DIVIDER_VIEW_TYPE;
+ item.position = pos;
+ return item;
+ }
+
+ public static AdapterItem asMarketSearch(int pos) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE;
+ item.position = pos;
+ return item;
+ }
}
/**
@@ -222,17 +248,17 @@ public class AlphabeticalAppsList {
}
/**
- * Returns the number of applications in this list.
+ * Returns the number of rows of applications (not including predictions)
*/
- public int getSize() {
- return mFilteredApps.size();
+ public int getNumAppRows() {
+ return mNumAppRowsInAdapter;
}
/**
- * Returns the number of rows of applications (not including predictions)
+ * Returns the number of applications in this list.
*/
- public int getNumAppRows() {
- return mNumAppRowsInAdapter;
+ public int getNumFilteredApps() {
+ return mFilteredApps.size();
}
/**
@@ -457,6 +483,16 @@ public class AlphabeticalAppsList {
mFilteredApps.add(info);
}
+ // Append the search market item if we are currently searching
+ if (hasFilter()) {
+ if (hasNoFilteredResults()) {
+ mAdapterItems.add(AdapterItem.asEmptySearch(position++));
+ } else {
+ mAdapterItems.add(AdapterItem.asDivider(position++));
+ }
+ mAdapterItems.add(AdapterItem.asMarketSearch(position++));
+ }
+
// Merge multiple sections together as requested by the merge strategy for this device
mergeSections();
@@ -484,18 +520,36 @@ public class AlphabeticalAppsList {
}
mNumAppRowsInAdapter = rowIndex + 1;
- // Pre-calculate all the fast scroller fractions based on the number of rows
- float rowFraction = 1f / mNumAppRowsInAdapter;
- for (FastScrollSectionInfo info : mFastScrollerSections) {
- AdapterItem item = info.fastScrollToItem;
- if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
- item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
- info.touchFraction = 0f;
- continue;
- }
-
- float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
- info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
+ // Pre-calculate all the fast scroller fractions
+ switch (mFastScrollDistributionMode) {
+ case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION:
+ float rowFraction = 1f / mNumAppRowsInAdapter;
+ for (FastScrollSectionInfo info : mFastScrollerSections) {
+ AdapterItem item = info.fastScrollToItem;
+ if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
+ item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
+ info.touchFraction = 0f;
+ continue;
+ }
+
+ float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
+ info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
+ }
+ break;
+ case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS:
+ float perSectionTouchFraction = 1f / mFastScrollerSections.size();
+ float cumulativeTouchFraction = 0f;
+ for (FastScrollSectionInfo info : mFastScrollerSections) {
+ AdapterItem item = info.fastScrollToItem;
+ if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
+ item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
+ info.touchFraction = 0f;
+ continue;
+ }
+ info.touchFraction = cumulativeTouchFraction;
+ cumulativeTouchFraction += perSectionTouchFraction;
+ }
+ break;
}
}
diff --git a/src/com/android/launcher3/allapps/DefaultAppSearchController.java b/src/com/android/launcher3/allapps/DefaultAppSearchController.java
index 83b920589..3169f842a 100644
--- a/src/com/android/launcher3/allapps/DefaultAppSearchController.java
+++ b/src/com/android/launcher3/allapps/DefaultAppSearchController.java
@@ -25,6 +25,7 @@ import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
+import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.Thunk;
@@ -54,7 +55,8 @@ final class DefaultAppSearchController extends AllAppsSearchBarController
@Thunk View mSearchBarContainerView;
private View mSearchButtonView;
private View mDismissSearchButtonView;
- @Thunk AllAppsSearchEditView mSearchBarEditView;
+ @Thunk
+ ExtendedEditText mSearchBarEditView;
@Thunk AllAppsRecyclerView mAppsRecyclerView;
@Thunk Runnable mFocusRecyclerViewRunnable = new Runnable() {
@Override
@@ -82,21 +84,23 @@ final class DefaultAppSearchController extends AllAppsSearchBarController
mSearchBarContainerView = mSearchView.findViewById(R.id.search_container);
mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
mDismissSearchButtonView.setOnClickListener(this);
- mSearchBarEditView = (AllAppsSearchEditView)
+ mSearchBarEditView = (ExtendedEditText)
mSearchBarContainerView.findViewById(R.id.search_box_input);
mSearchBarEditView.addTextChangedListener(this);
mSearchBarEditView.setOnEditorActionListener(this);
mSearchBarEditView.setOnBackKeyListener(
- new AllAppsSearchEditView.OnBackKeyListener() {
+ new ExtendedEditText.OnBackKeyListener() {
@Override
- public void onBackKey() {
+ public boolean onBackKey() {
// Only hide the search field if there is no query, or if there
// are no filtered results
String query = Utilities.trim(
mSearchBarEditView.getEditableText().toString());
if (query.isEmpty() || mApps.hasNoFilteredResults()) {
hideSearchField(true, mFocusRecyclerViewRunnable);
+ return true;
}
+ return false;
}
});
return mSearchView;
@@ -166,22 +170,24 @@ final class DefaultAppSearchController extends AllAppsSearchBarController
return false;
}
// Skip if it's not the right action
- if (actionId != EditorInfo.IME_ACTION_DONE) {
+ if (actionId != EditorInfo.IME_ACTION_SEARCH) {
return false;
}
- // Skip if there isn't exactly one item
- if (mApps.getSize() != 1) {
+ // Skip if there are more than one icon
+ if (mApps.getNumFilteredApps() > 1) {
return false;
}
- // If there is exactly one icon, then quick-launch it
+ // Otherwise, find the first icon, or fallback to the search-market-view and launch it
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
for (int i = 0; i < items.size(); i++) {
AlphabeticalAppsList.AdapterItem item = items.get(i);
- if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
- mAppsRecyclerView.getChildAt(i).performClick();
- mInputMethodManager.hideSoftInputFromWindow(
- mContainerView.getWindowToken(), 0);
- return true;
+ switch (item.viewType) {
+ case AllAppsGridAdapter.ICON_VIEW_TYPE:
+ case AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE:
+ mAppsRecyclerView.getChildAt(i).performClick();
+ mInputMethodManager.hideSoftInputFromWindow(
+ mContainerView.getWindowToken(), 0);
+ return true;
}
}
return false;
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
index 7aa36d447..434f13dcc 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
@@ -40,7 +40,7 @@ public abstract class AppWidgetManagerCompat {
public static AppWidgetManagerCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
sInstance = new AppWidgetManagerCompatVL(context.getApplicationContext());
} else {
sInstance = new AppWidgetManagerCompatV16(context.getApplicationContext());
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
index f7f4b7e4f..463cf902d 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
@@ -54,10 +54,10 @@ class AppWidgetManagerCompatV16 extends AppWidgetManagerCompat {
@Override
public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,
Bundle options) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
- return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider);
- } else {
+ if (Utilities.ATLEAST_JB_MR1) {
return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider, options);
+ } else {
+ return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider);
}
}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 5858bc8b9..95e3ba902 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -53,7 +53,7 @@ public abstract class LauncherAppsCompat {
public static LauncherAppsCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
sInstance = new LauncherAppsCompatVL(context.getApplicationContext());
} else {
sInstance = new LauncherAppsCompatV16(context.getApplicationContext());
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
index ac3d252f5..339c457e1 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
@@ -31,6 +31,7 @@ import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
+import com.android.launcher3.Utilities;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
@@ -188,7 +189,7 @@ public class LauncherAppsCompatV16 extends LauncherAppsCompat {
// when moving a package or mounting/un-mounting external storage. Assume that
// it is a replacing operation.
final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING,
- Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT);
+ !Utilities.ATLEAST_KITKAT);
String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
callback.onPackagesAvailable(packages, user, replacing);
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java
index c49908328..ec5014d7c 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompat.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java
@@ -34,7 +34,7 @@ public abstract class PackageInstallerCompat {
public static PackageInstallerCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
sInstance = new PackageInstallerCompatVL(context);
} else {
sInstance = new PackageInstallerCompatV16();
diff --git a/src/com/android/launcher3/compat/UserHandleCompat.java b/src/com/android/launcher3/compat/UserHandleCompat.java
index ab4b7216b..567022b43 100644
--- a/src/com/android/launcher3/compat/UserHandleCompat.java
+++ b/src/com/android/launcher3/compat/UserHandleCompat.java
@@ -34,7 +34,7 @@ public class UserHandleCompat {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static UserHandleCompat myUserHandle() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
return new UserHandleCompat(android.os.Process.myUserHandle());
} else {
return new UserHandleCompat();
@@ -55,7 +55,7 @@ public class UserHandleCompat {
@Override
public String toString() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
return mUser.toString();
} else {
return "";
@@ -67,7 +67,7 @@ public class UserHandleCompat {
if (!(other instanceof UserHandleCompat)) {
return false;
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
return mUser.equals(((UserHandleCompat) other).mUser);
} else {
return true;
@@ -76,7 +76,7 @@ public class UserHandleCompat {
@Override
public int hashCode() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
return mUser.hashCode();
} else {
return 0;
@@ -89,7 +89,7 @@ public class UserHandleCompat {
* profiles so this is a no-op.
*/
public void addToIntent(Intent intent, String name) {
- if (Utilities.isLmpOrAbove() && mUser != null) {
+ if (Utilities.ATLEAST_LOLLIPOP && mUser != null) {
intent.putExtra(name, mUser);
}
}
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index a79d94646..f708004a3 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -28,16 +28,29 @@ public abstract class UserManagerCompat {
protected UserManagerCompat() {
}
+ private static final Object sInstanceLock = new Object();
+ private static UserManagerCompat sInstance;
+
public static UserManagerCompat getInstance(Context context) {
- if (Utilities.isLmpOrAbove()) {
- return new UserManagerCompatVL(context);
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- return new UserManagerCompatV17(context);
- } else {
- return new UserManagerCompatV16();
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
+ sInstance = new UserManagerCompatVL(context.getApplicationContext());
+ } else if (Utilities.ATLEAST_JB_MR1) {
+ sInstance = new UserManagerCompatV17(context.getApplicationContext());
+ } else {
+ sInstance = new UserManagerCompatV16();
+ }
+ }
+ return sInstance;
}
}
+ /**
+ * Creates a cache for users.
+ */
+ public abstract void enableAndResetCache();
+
public abstract List<UserHandleCompat> getUserProfiles();
public abstract long getSerialNumberForUser(UserHandleCompat user);
public abstract UserHandleCompat getUserForSerialNumber(long serialNumber);
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java
index ffe698c8b..85aee57e8 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatV16.java
@@ -53,4 +53,8 @@ public class UserManagerCompatV16 extends UserManagerCompat {
public long getUserCreationTime(UserHandleCompat user) {
return 0;
}
+
+ @Override
+ public void enableAndResetCache() {
+ }
}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV17.java b/src/com/android/launcher3/compat/UserManagerCompatV17.java
index c42c00c7d..75203b7f3 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatV17.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatV17.java
@@ -21,8 +21,18 @@ import android.content.Context;
import android.os.Build;
import android.os.UserManager;
+import com.android.launcher3.util.LongArrayMap;
+
+import java.util.HashMap;
+
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public class UserManagerCompatV17 extends UserManagerCompatV16 {
+
+ protected LongArrayMap<UserHandleCompat> mUsers;
+ // Create a separate reverse map as LongArrayMap.indexOfValue checks if objects are same
+ // and not {@link Object#equals}
+ protected HashMap<UserHandleCompat, Long> mUserToSerialMap;
+
protected UserManager mUserManager;
UserManagerCompatV17(Context context) {
@@ -30,11 +40,34 @@ public class UserManagerCompatV17 extends UserManagerCompatV16 {
}
public long getSerialNumberForUser(UserHandleCompat user) {
+ synchronized (this) {
+ if (mUserToSerialMap != null) {
+ Long serial = mUserToSerialMap.get(user);
+ return serial == null ? 0 : serial;
+ }
+ }
return mUserManager.getSerialNumberForUser(user.getUser());
}
public UserHandleCompat getUserForSerialNumber(long serialNumber) {
+ synchronized (this) {
+ if (mUsers != null) {
+ return mUsers.get(serialNumber);
+ }
+ }
return UserHandleCompat.fromUser(mUserManager.getUserForSerialNumber(serialNumber));
}
+
+ @Override
+ public void enableAndResetCache() {
+ synchronized (this) {
+ mUsers = new LongArrayMap<>();
+ mUserToSerialMap = new HashMap<>();
+ UserHandleCompat myUser = UserHandleCompat.myUserHandle();
+ long serial = mUserManager.getSerialNumberForUser(myUser.getUser());
+ mUsers.put(serial, myUser);
+ mUserToSerialMap.put(myUser, serial);
+ }
+ }
}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index dd7a72617..dc3ec3cd8 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -24,9 +24,14 @@ import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.UserHandle;
+
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.LongArrayMap;
+
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -43,7 +48,32 @@ public class UserManagerCompatVL extends UserManagerCompatV17 {
}
@Override
+ public void enableAndResetCache() {
+ synchronized (this) {
+ mUsers = new LongArrayMap<>();
+ mUserToSerialMap = new HashMap<>();
+ List<UserHandle> users = mUserManager.getUserProfiles();
+ if (users != null) {
+ for (UserHandle user : users) {
+ long serial = mUserManager.getSerialNumberForUser(user);
+ UserHandleCompat userCompat = UserHandleCompat.fromUser(user);
+ mUsers.put(serial, userCompat);
+ mUserToSerialMap.put(userCompat, serial);
+ }
+ }
+ }
+ }
+
+ @Override
public List<UserHandleCompat> getUserProfiles() {
+ synchronized (this) {
+ if (mUsers != null) {
+ List<UserHandleCompat> users = new ArrayList<>();
+ users.addAll(mUserToSerialMap.keySet());
+ return users;
+ }
+ }
+
List<UserHandle> users = mUserManager.getUserProfiles();
if (users == null) {
return Collections.emptyList();
@@ -71,7 +101,9 @@ public class UserManagerCompatVL extends UserManagerCompatV17 {
@Override
public long getUserCreationTime(UserHandleCompat user) {
- // TODO: Use system API once available.
+ if (Utilities.ATLEAST_MARSHMALLOW) {
+ return mUserManager.getUserCreationTime(user.getUser());
+ }
SharedPreferences prefs = mContext.getSharedPreferences(
LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
String key = USER_CREATION_TIME_KEY + getSerialNumberForUser(user);
diff --git a/src/com/android/launcher3/model/AbstractUserComparator.java b/src/com/android/launcher3/model/AbstractUserComparator.java
index cf47ce648..bd28560f3 100644
--- a/src/com/android/launcher3/model/AbstractUserComparator.java
+++ b/src/com/android/launcher3/model/AbstractUserComparator.java
@@ -22,14 +22,12 @@ import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import java.util.Comparator;
-import java.util.HashMap;
/**
* A comparator to arrange items based on user profiles.
*/
public abstract class AbstractUserComparator<T extends ItemInfo> implements Comparator<T> {
- private HashMap<UserHandleCompat, Long> mUserSerialCache = new HashMap<>();
private final UserManagerCompat mUserManager;
private final UserHandleCompat mMyUser;
@@ -43,25 +41,9 @@ public abstract class AbstractUserComparator<T extends ItemInfo> implements Comp
if (mMyUser.equals(lhs.user)) {
return -1;
} else {
- Long aUserSerial = getAndCacheUserSerial(lhs.user);
- Long bUserSerial = getAndCacheUserSerial(rhs.user);
+ Long aUserSerial = mUserManager.getSerialNumberForUser(lhs.user);
+ Long bUserSerial = mUserManager.getSerialNumberForUser(rhs.user);
return aUserSerial.compareTo(bUserSerial);
}
}
-
- /**
- * Returns the user serial for this user, using a cached serial if possible.
- */
- private Long getAndCacheUserSerial(UserHandleCompat user) {
- Long userSerial = mUserSerialCache.get(user);
- if (userSerial == null) {
- userSerial = mUserManager.getSerialNumberForUser(user);
- mUserSerialCache.put(user, userSerial);
- }
- return userSerial;
- }
-
- public void clearUserCache() {
- mUserSerialCache.clear();
- }
}
diff --git a/src/com/android/launcher3/model/AppNameComparator.java b/src/com/android/launcher3/model/AppNameComparator.java
index c4b74d4dc..5f80037dc 100644
--- a/src/com/android/launcher3/model/AppNameComparator.java
+++ b/src/com/android/launcher3/model/AppNameComparator.java
@@ -68,8 +68,6 @@ public class AppNameComparator {
* Returns a locale-aware comparator that will alphabetically order a list of applications.
*/
public Comparator<ItemInfo> getAppInfoComparator() {
- // Clear the user serial cache so that we get serials as needed in the comparator
- mAppInfoComparator.clearUserCache();
return mAppInfoComparator;
}
diff --git a/src/com/android/launcher3/model/MigrateFromRestoreTask.java b/src/com/android/launcher3/model/MigrateFromRestoreTask.java
new file mode 100644
index 000000000..6a529f61f
--- /dev/null
+++ b/src/com/android/launcher3/model/MigrateFromRestoreTask.java
@@ -0,0 +1,767 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.database.Cursor;
+import android.graphics.Point;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.Thunk;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
+ * result of restoring from a larger device.
+ */
+public class MigrateFromRestoreTask {
+
+ public static boolean ENABLED = false;
+
+ private static final String TAG = "MigrateFromRestoreTask";
+ private static final boolean DEBUG = true;
+
+ private static final String KEY_MIGRATION_SOURCE_SIZE = "migration_restore_src_size";
+ private static final String KEY_MIGRATION_WIDGET_MINSIZE = "migration_widget_min_size";
+
+ // These are carefully selected weights for various item types (Math.random?), to allow for
+ // the lease absurd migration experience.
+ private static final float WT_SHORTCUT = 1;
+ private static final float WT_APPLICATION = 0.8f;
+ private static final float WT_WIDGET_MIN = 2;
+ private static final float WT_WIDGET_FACTOR = 0.6f;
+ private static final float WT_FOLDER_FACTOR = 0.5f;
+
+ private final Context mContext;
+ private final ContentValues mTempValues = new ContentValues();
+ private final HashMap<String, Point> mWidgetMinSize;
+ private final InvariantDeviceProfile mIdp;
+
+ private HashSet<String> mValidPackages;
+ public ArrayList<Long> mEntryToRemove;
+ private ArrayList<ContentProviderOperation> mUpdateOperations;
+
+ private ArrayList<DbEntry> mCarryOver;
+
+ private final int mSrcX, mSrcY;
+ @Thunk final int mTrgX, mTrgY;
+ private final boolean mShouldRemoveX, mShouldRemoveY;
+
+ public MigrateFromRestoreTask(Context context) {
+ mContext = context;
+
+ SharedPreferences prefs = prefs(context);
+ Point sourceSize = parsePoint(prefs.getString(KEY_MIGRATION_SOURCE_SIZE, ""));
+ mSrcX = sourceSize.x;
+ mSrcY = sourceSize.y;
+
+ mWidgetMinSize = new HashMap<String, Point>();
+ for (String s : prefs.getStringSet(KEY_MIGRATION_WIDGET_MINSIZE,
+ Collections.<String>emptySet())) {
+ String[] parts = s.split("#");
+ mWidgetMinSize.put(parts[0], parsePoint(parts[1]));
+ }
+
+ mIdp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+ mTrgX = mIdp.numColumns;
+ mTrgY = mIdp.numRows;
+ mShouldRemoveX = mTrgX < mSrcX;
+ mShouldRemoveY = mTrgY < mSrcY;
+ }
+
+ public void execute() throws Exception {
+ mEntryToRemove = new ArrayList<>();
+ mCarryOver = new ArrayList<>();
+ mUpdateOperations = new ArrayList<>();
+
+ // Initialize list of valid packages. This contain all the packages which are already on
+ // the device and packages which are being installed. Any item which doesn't belong to
+ // this set is removed.
+ // Since the loader removes such items anyway, removing these items here doesn't cause any
+ // extra data loss and gives us more free space on the grid for better migration.
+ mValidPackages = new HashSet<>();
+ for (PackageInfo info : mContext.getPackageManager().getInstalledPackages(0)) {
+ mValidPackages.add(info.packageName);
+ }
+ mValidPackages.addAll(PackageInstallerCompat.getInstance(mContext)
+ .updateAndGetActiveSessionCache().keySet());
+
+ ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
+ if (allScreens.isEmpty()) {
+ throw new Exception("Unable to get workspace screens");
+ }
+
+ for (long screenId : allScreens) {
+ if (DEBUG) {
+ Log.d(TAG, "Migrating " + screenId);
+ }
+ migrateScreen(screenId);
+ }
+
+ if (!mCarryOver.isEmpty()) {
+ LongArrayMap<DbEntry> itemMap = new LongArrayMap<>();
+ for (DbEntry e : mCarryOver) {
+ itemMap.put(e.id, e);
+ }
+
+ do {
+ // Some items are still remaining. Try adding a few new screens.
+
+ // At every iteration, make sure that at least one item is removed from
+ // {@link #mCarryOver}, to prevent an infinite loop. If no item could be removed,
+ // break the loop and abort migration by throwing an exception.
+ OptimalPlacementSolution placement = new OptimalPlacementSolution(
+ new boolean[mTrgX][mTrgY], deepCopy(mCarryOver), true);
+ placement.find();
+ if (placement.finalPlacedItems.size() > 0) {
+ long newScreenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
+ allScreens.add(newScreenId);
+ for (DbEntry item : placement.finalPlacedItems) {
+ if (!mCarryOver.remove(itemMap.get(item.id))) {
+ throw new Exception("Unable to find matching items");
+ }
+ item.screenId = newScreenId;
+ update(item);
+ }
+ } else {
+ throw new Exception("None of the items can be placed on an empty screen");
+ }
+
+ } while (!mCarryOver.isEmpty());
+
+
+ LauncherAppState.getInstance().getModel()
+ .updateWorkspaceScreenOrder(mContext, allScreens);
+ }
+
+ // Update items
+ mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, mUpdateOperations);
+
+ if (!mEntryToRemove.isEmpty()) {
+ if (DEBUG) {
+ Log.d(TAG, "Removing items: " + TextUtils.join(", ", mEntryToRemove));
+ }
+ mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
+ Utilities.createDbSelectionQuery(
+ LauncherSettings.Favorites._ID, mEntryToRemove), null);
+ }
+
+ // Make sure we haven't removed everything.
+ final Cursor c = mContext.getContentResolver().query(
+ LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
+ boolean hasData = c.moveToNext();
+ c.close();
+ if (!hasData) {
+ throw new Exception("Removed every thing during grid resize");
+ }
+ }
+
+ /**
+ * Migrate a particular screen id.
+ * Strategy:
+ * 1) For all possible combinations of row and column, pick the one which causes the least
+ * data loss: {@link #tryRemove(int, int, ArrayList, float[])}
+ * 2) Maintain a list of all lost items before this screen, and add any new item lost from
+ * this screen to that list as well.
+ * 3) If all those items from the above list can be placed on this screen, place them
+ * (otherwise they are placed on a new screen).
+ */
+ private void migrateScreen(long screenId) {
+ ArrayList<DbEntry> items = loadEntries(screenId);
+
+ int removedCol = Integer.MAX_VALUE;
+ int removedRow = Integer.MAX_VALUE;
+
+ // removeWt represents the cost function for loss of items during migration, and moveWt
+ // represents the cost function for repositioning the items. moveWt is only considered if
+ // removeWt is same for two different configurations.
+ // Start with Float.MAX_VALUE (assuming full data) and pick the configuration with least
+ // cost.
+ float removeWt = Float.MAX_VALUE;
+ float moveWt = Float.MAX_VALUE;
+ float[] outLoss = new float[2];
+ ArrayList<DbEntry> finalItems = null;
+
+ // Try removing all possible combinations
+ for (int x = 0; x < mSrcX; x++) {
+ for (int y = 0; y < mSrcY; y++) {
+ // Use a deep copy when trying out a particular combination as it can change
+ // the underlying object.
+ ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, deepCopy(items), outLoss);
+
+ if ((outLoss[0] < removeWt) || ((outLoss[0] == removeWt) && (outLoss[1] < moveWt))) {
+ removeWt = outLoss[0];
+ moveWt = outLoss[1];
+ removedCol = mShouldRemoveX ? x : removedCol;
+ removedRow = mShouldRemoveY ? y : removedRow;
+ finalItems = itemsOnScreen;
+ }
+
+ // No need to loop over all rows, if a row removal is not needed.
+ if (!mShouldRemoveY) {
+ break;
+ }
+ }
+
+ if (!mShouldRemoveX) {
+ break;
+ }
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, String.format("Removing row %d, column %d on screen %d",
+ removedRow, removedCol, screenId));
+ }
+
+ LongArrayMap<DbEntry> itemMap = new LongArrayMap<>();
+ for (DbEntry e : deepCopy(items)) {
+ itemMap.put(e.id, e);
+ }
+
+ for (DbEntry item : finalItems) {
+ DbEntry org = itemMap.get(item.id);
+ itemMap.remove(item.id);
+
+ // Check if update is required
+ if (!item.columnsSame(org)) {
+ update(item);
+ }
+ }
+
+ // The remaining items in {@link #itemMap} are those which didn't get placed.
+ for (DbEntry item : itemMap) {
+ mCarryOver.add(item);
+ }
+
+ if (!mCarryOver.isEmpty() && removeWt == 0) {
+ // No new items were removed in this step. Try placing all the items on this screen.
+ boolean[][] occupied = new boolean[mTrgX][mTrgY];
+ for (DbEntry item : finalItems) {
+ markCells(occupied, item, true);
+ }
+
+ OptimalPlacementSolution placement = new OptimalPlacementSolution(occupied,
+ deepCopy(mCarryOver), true);
+ placement.find();
+ if (placement.lowestWeightLoss == 0) {
+ // All items got placed
+
+ for (DbEntry item : placement.finalPlacedItems) {
+ item.screenId = screenId;
+ update(item);
+ }
+
+ mCarryOver.clear();
+ }
+ }
+ }
+
+ /**
+ * Updates an item in the DB.
+ */
+ private void update(DbEntry item) {
+ mTempValues.clear();
+ item.addToContentValues(mTempValues);
+ mUpdateOperations.add(ContentProviderOperation
+ .newUpdate(LauncherSettings.Favorites.getContentUri(item.id))
+ .withValues(mTempValues).build());
+ }
+
+ /**
+ * Tries the remove the provided row and column.
+ * @param items all the items on the screen under operation
+ * @param outLoss array of size 2. The first entry is filled with weight loss, and the second
+ * with the overall item movement.
+ */
+ private ArrayList<DbEntry> tryRemove(int col, int row, ArrayList<DbEntry> items,
+ float[] outLoss) {
+ boolean[][] occupied = new boolean[mTrgX][mTrgY];
+
+ col = mShouldRemoveX ? col : Integer.MAX_VALUE;
+ row = mShouldRemoveY ? row : Integer.MAX_VALUE;
+
+ ArrayList<DbEntry> finalItems = new ArrayList<>();
+ ArrayList<DbEntry> removedItems = new ArrayList<>();
+
+ for (DbEntry item : items) {
+ if ((item.cellX <= col && (item.spanX + item.cellX) > col)
+ || (item.cellY <= row && (item.spanY + item.cellY) > row)) {
+ removedItems.add(item);
+ if (item.cellX >= col) item.cellX --;
+ if (item.cellY >= row) item.cellY --;
+ } else {
+ if (item.cellX > col) item.cellX --;
+ if (item.cellY > row) item.cellY --;
+ finalItems.add(item);
+ markCells(occupied, item, true);
+ }
+ }
+
+ OptimalPlacementSolution placement = new OptimalPlacementSolution(occupied, removedItems);
+ placement.find();
+ finalItems.addAll(placement.finalPlacedItems);
+ outLoss[0] = placement.lowestWeightLoss;
+ outLoss[1] = placement.lowestMoveCost;
+ return finalItems;
+ }
+
+ @Thunk void markCells(boolean[][] occupied, DbEntry item, boolean val) {
+ for (int i = item.cellX; i < (item.cellX + item.spanX); i++) {
+ for (int j = item.cellY; j < (item.cellY + item.spanY); j++) {
+ occupied[i][j] = val;
+ }
+ }
+ }
+
+ @Thunk boolean isVacant(boolean[][] occupied, int x, int y, int w, int h) {
+ if (x + w > mTrgX) return false;
+ if (y + h > mTrgY) return false;
+
+ for (int i = 0; i < w; i++) {
+ for (int j = 0; j < h; j++) {
+ if (occupied[i + x][j + y]) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private class OptimalPlacementSolution {
+ private final ArrayList<DbEntry> itemsToPlace;
+ private final boolean[][] occupied;
+
+ // If set to true, item movement are not considered in move cost, leading to a more
+ // linear placement.
+ private final boolean ignoreMove;
+
+ float lowestWeightLoss = Float.MAX_VALUE;
+ float lowestMoveCost = Float.MAX_VALUE;
+ ArrayList<DbEntry> finalPlacedItems;
+
+ public OptimalPlacementSolution(boolean[][] occupied, ArrayList<DbEntry> itemsToPlace) {
+ this(occupied, itemsToPlace, false);
+ }
+
+ public OptimalPlacementSolution(boolean[][] occupied, ArrayList<DbEntry> itemsToPlace,
+ boolean ignoreMove) {
+ this.occupied = occupied;
+ this.itemsToPlace = itemsToPlace;
+ this.ignoreMove = ignoreMove;
+
+ // Sort the items such that larger widgets appear first followed by 1x1 items
+ Collections.sort(this.itemsToPlace);
+ }
+
+ public void find() {
+ find(0, 0, 0, new ArrayList<DbEntry>());
+ }
+
+ /**
+ * Recursively finds a placement for the provided items.
+ * @param index the position in {@link #itemsToPlace} to start looking at.
+ * @param weightLoss total weight loss upto this point
+ * @param moveCost total move cost upto this point
+ * @param itemsPlaced all the items already placed upto this point
+ */
+ public void find(int index, float weightLoss, float moveCost,
+ ArrayList<DbEntry> itemsPlaced) {
+ if ((weightLoss >= lowestWeightLoss) ||
+ ((weightLoss == lowestWeightLoss) && (moveCost >= lowestMoveCost))) {
+ // Abort, as we already have a better solution.
+ return;
+
+ } else if (index >= itemsToPlace.size()) {
+ // End loop.
+ lowestWeightLoss = weightLoss;
+ lowestMoveCost = moveCost;
+
+ // Keep a deep copy of current configuration as it can change during recursion.
+ finalPlacedItems = deepCopy(itemsPlaced);
+ return;
+ }
+
+ DbEntry me = itemsToPlace.get(index);
+ int myX = me.cellX;
+ int myY = me.cellY;
+
+ // List of items to pass over if this item was placed.
+ ArrayList<DbEntry> itemsIncludingMe = new ArrayList<>(itemsPlaced.size() + 1);
+ itemsIncludingMe.addAll(itemsPlaced);
+ itemsIncludingMe.add(me);
+
+ if (me.spanX > 1 || me.spanY > 1) {
+ // If the current item is a widget (and it greater than 1x1), try to place it at
+ // all possible positions. This is because a widget placed at one position can
+ // affect the placement of a different widget.
+ int myW = me.spanX;
+ int myH = me.spanY;
+
+ for (int y = 0; y < mTrgY; y++) {
+ for (int x = 0; x < mTrgX; x++) {
+ float newMoveCost = moveCost;
+ if (x != myX) {
+ me.cellX = x;
+ newMoveCost ++;
+ }
+ if (y != myY) {
+ me.cellY = y;
+ newMoveCost ++;
+ }
+ if (ignoreMove) {
+ newMoveCost = moveCost;
+ }
+
+ if (isVacant(occupied, x, y, myW, myH)) {
+ // place at this position and continue search.
+ markCells(occupied, me, true);
+ find(index + 1, weightLoss, newMoveCost, itemsIncludingMe);
+ markCells(occupied, me, false);
+ }
+
+ // Try resizing horizontally
+ if (myW > me.minSpanX && isVacant(occupied, x, y, myW - 1, myH)) {
+ me.spanX --;
+ markCells(occupied, me, true);
+ // 1 extra move cost
+ find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
+ markCells(occupied, me, false);
+ me.spanX ++;
+ }
+
+ // Try resizing vertically
+ if (myH > me.minSpanY && isVacant(occupied, x, y, myW, myH - 1)) {
+ me.spanY --;
+ markCells(occupied, me, true);
+ // 1 extra move cost
+ find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
+ markCells(occupied, me, false);
+ me.spanY ++;
+ }
+
+ // Try resizing horizontally & vertically
+ if (myH > me.minSpanY && myW > me.minSpanX &&
+ isVacant(occupied, x, y, myW - 1, myH - 1)) {
+ me.spanX --;
+ me.spanY --;
+ markCells(occupied, me, true);
+ // 2 extra move cost
+ find(index + 1, weightLoss, newMoveCost + 2, itemsIncludingMe);
+ markCells(occupied, me, false);
+ me.spanX ++;
+ me.spanY ++;
+ }
+ me.cellX = myX;
+ me.cellY = myY;
+ }
+ }
+
+ // Finally also try a solution when this item is not included. Trying it in the end
+ // causes it to get skipped in most cases due to higher weight loss, and prevents
+ // unnecessary deep copies of various configurations.
+ find(index + 1, weightLoss + me.weight, moveCost, itemsPlaced);
+ } else {
+ // Since this is a 1x1 item and all the following items are also 1x1, just place
+ // it at 'the most appropriate position' and hope for the best.
+ // The most appropriate position: one with lease straight line distance
+ int newDistance = Integer.MAX_VALUE;
+ int newX = Integer.MAX_VALUE, newY = Integer.MAX_VALUE;
+
+ for (int y = 0; y < mTrgY; y++) {
+ for (int x = 0; x < mTrgX; x++) {
+ if (!occupied[x][y]) {
+ int dist = ignoreMove ? 0 :
+ ((me.cellX - x) * (me.cellX - x) + (me.cellY - y) * (me.cellY - y));
+ if (dist < newDistance) {
+ newX = x;
+ newY = y;
+ newDistance = dist;
+ }
+ }
+ }
+ }
+
+ if (newX < mTrgX && newY < mTrgY) {
+ float newMoveCost = moveCost;
+ if (newX != myX) {
+ me.cellX = newX;
+ newMoveCost ++;
+ }
+ if (newY != myY) {
+ me.cellY = newY;
+ newMoveCost ++;
+ }
+ if (ignoreMove) {
+ newMoveCost = moveCost;
+ }
+ markCells(occupied, me, true);
+ find(index + 1, weightLoss, newMoveCost, itemsIncludingMe);
+ markCells(occupied, me, false);
+ me.cellX = myX;
+ me.cellY = myY;
+
+ // Try to find a solution without this item, only if
+ // 1) there was at least one space, i.e., we were able to place this item
+ // 2) if the next item has the same weight (all items are already sorted), as
+ // if it has lower weight, that solution will automatically get discarded.
+ // 3) ignoreMove false otherwise, move cost is ignored and the weight will
+ // anyway be same.
+ if (index + 1 < itemsToPlace.size()
+ && itemsToPlace.get(index + 1).weight >= me.weight && !ignoreMove) {
+ find(index + 1, weightLoss + me.weight, moveCost, itemsPlaced);
+ }
+ } else {
+ // No more space. Jump to the end.
+ for (int i = index + 1; i < itemsToPlace.size(); i++) {
+ weightLoss += itemsToPlace.get(i).weight;
+ }
+ find(itemsToPlace.size(), weightLoss + me.weight, moveCost, itemsPlaced);
+ }
+ }
+ }
+ }
+
+ /**
+ * Loads entries for a particular screen id.
+ */
+ public ArrayList<DbEntry> loadEntries(long screen) {
+ Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+ new String[] {
+ Favorites._ID, // 0
+ Favorites.ITEM_TYPE, // 1
+ Favorites.CELLX, // 2
+ Favorites.CELLY, // 3
+ Favorites.SPANX, // 4
+ Favorites.SPANY, // 5
+ Favorites.INTENT, // 6
+ Favorites.APPWIDGET_PROVIDER}, // 7
+ Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP
+ + " AND " + Favorites.SCREEN + " = " + screen, null, null, null);
+
+ final int indexId = c.getColumnIndexOrThrow(Favorites._ID);
+ final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE);
+ final int indexCellX = c.getColumnIndexOrThrow(Favorites.CELLX);
+ final int indexCellY = c.getColumnIndexOrThrow(Favorites.CELLY);
+ final int indexSpanX = c.getColumnIndexOrThrow(Favorites.SPANX);
+ final int indexSpanY = c.getColumnIndexOrThrow(Favorites.SPANY);
+ final int indexIntent = c.getColumnIndexOrThrow(Favorites.INTENT);
+ final int indexAppWidgetProvider = c.getColumnIndexOrThrow(Favorites.APPWIDGET_PROVIDER);
+
+ ArrayList<DbEntry> entries = new ArrayList<>();
+ while (c.moveToNext()) {
+ DbEntry entry = new DbEntry();
+ entry.id = c.getLong(indexId);
+ entry.itemType = c.getInt(indexItemType);
+ entry.cellX = c.getInt(indexCellX);
+ entry.cellY = c.getInt(indexCellY);
+ entry.spanX = c.getInt(indexSpanX);
+ entry.spanY = c.getInt(indexSpanY);
+ entry.screenId = screen;
+
+ try {
+ // calculate weight
+ switch (entry.itemType) {
+ case Favorites.ITEM_TYPE_SHORTCUT:
+ case Favorites.ITEM_TYPE_APPLICATION: {
+ verifyIntent(c.getString(indexIntent));
+ entry.weight = entry.itemType == Favorites.ITEM_TYPE_SHORTCUT
+ ? WT_SHORTCUT : WT_APPLICATION;
+ break;
+ }
+ case Favorites.ITEM_TYPE_APPWIDGET: {
+ String provider = c.getString(indexAppWidgetProvider);
+ ComponentName cn = ComponentName.unflattenFromString(provider);
+ verifyPackage(cn.getPackageName());
+ entry.weight = Math.max(WT_WIDGET_MIN, WT_WIDGET_FACTOR
+ * entry.spanX * entry.spanY);
+
+ // Migration happens for current user only.
+ LauncherAppWidgetProviderInfo pInfo = LauncherModel.getProviderInfo(
+ mContext, cn, UserHandleCompat.myUserHandle());
+ Point spans = pInfo == null ?
+ mWidgetMinSize.get(provider) : pInfo.getMinSpans(mIdp, mContext);
+ if (spans != null) {
+ entry.minSpanX = spans.x > 0 ? spans.x : entry.spanX;
+ entry.minSpanY = spans.y > 0 ? spans.y : entry.spanY;
+ } else {
+ // Assume that the widget be resized down to 2x2
+ entry.minSpanX = entry.minSpanY = 2;
+ }
+
+ if (entry.minSpanX > mTrgX || entry.minSpanY > mTrgY) {
+ throw new Exception("Widget can't be resized down to fit the grid");
+ }
+ break;
+ }
+ case Favorites.ITEM_TYPE_FOLDER: {
+ int total = getFolderItemsCount(entry.id);
+ if (total == 0) {
+ throw new Exception("Folder is empty");
+ }
+ entry.weight = WT_FOLDER_FACTOR * total;
+ break;
+ }
+ default:
+ throw new Exception("Invalid item type");
+ }
+ } catch (Exception e) {
+ if (DEBUG) {
+ Log.d(TAG, "Removing item " + entry.id, e);
+ }
+ mEntryToRemove.add(entry.id);
+ continue;
+ }
+
+ entries.add(entry);
+ }
+ return entries;
+ }
+
+ /**
+ * @return the number of valid items in the folder.
+ */
+ private int getFolderItemsCount(long folderId) {
+ Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+ new String[] {Favorites._ID, Favorites.INTENT},
+ Favorites.CONTAINER + " = " + folderId, null, null, null);
+
+ int total = 0;
+ while (c.moveToNext()) {
+ try {
+ verifyIntent(c.getString(1));
+ total++;
+ } catch (Exception e) {
+ mEntryToRemove.add(c.getLong(0));
+ }
+ }
+
+ return total;
+ }
+
+ /**
+ * Verifies if the intent should be restored.
+ */
+ private void verifyIntent(String intentStr) throws Exception {
+ Intent intent = Intent.parseUri(intentStr, 0);
+ if (intent.getComponent() != null) {
+ verifyPackage(intent.getComponent().getPackageName());
+ } else if (intent.getPackage() != null) {
+ // Only verify package if the component was null.
+ verifyPackage(intent.getPackage());
+ }
+ }
+
+ /**
+ * Verifies if the package should be restored
+ */
+ private void verifyPackage(String packageName) throws Exception {
+ if (!mValidPackages.contains(packageName)) {
+ throw new Exception("Package not available");
+ }
+ }
+
+ private static class DbEntry extends ItemInfo implements Comparable<DbEntry> {
+
+ public float weight;
+
+ public DbEntry() { }
+
+ public DbEntry copy() {
+ DbEntry entry = new DbEntry();
+ entry.copyFrom(this);
+ entry.weight = weight;
+ entry.minSpanX = minSpanX;
+ entry.minSpanY = minSpanY;
+ return entry;
+ }
+
+ /**
+ * Comparator such that larger widgets come first, followed by all 1x1 items
+ * based on their weights.
+ */
+ @Override
+ public int compareTo(DbEntry another) {
+ if (itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+ if (another.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+ return another.spanY * another.spanX - spanX * spanY;
+ } else {
+ return -1;
+ }
+ } else if (another.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+ return 1;
+ } else {
+ // Place higher weight before lower weight.
+ return Float.compare(another.weight, weight);
+ }
+ }
+
+ public boolean columnsSame(DbEntry org) {
+ return org.cellX == cellX && org.cellY == cellY && org.spanX == spanX &&
+ org.spanY == spanY && org.screenId == screenId;
+ }
+
+ public void addToContentValues(ContentValues values) {
+ values.put(LauncherSettings.Favorites.SCREEN, screenId);
+ values.put(LauncherSettings.Favorites.CELLX, cellX);
+ values.put(LauncherSettings.Favorites.CELLY, cellY);
+ values.put(LauncherSettings.Favorites.SPANX, spanX);
+ values.put(LauncherSettings.Favorites.SPANY, spanY);
+ }
+ }
+
+ @Thunk static ArrayList<DbEntry> deepCopy(ArrayList<DbEntry> src) {
+ ArrayList<DbEntry> dup = new ArrayList<DbEntry>(src.size());
+ for (DbEntry e : src) {
+ dup.add(e.copy());
+ }
+ return dup;
+ }
+
+ private static Point parsePoint(String point) {
+ String[] split = point.split(",");
+ return new Point(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
+ }
+
+ public static void markForMigration(Context context, int srcX, int srcY,
+ HashSet<String> widgets) {
+ prefs(context).edit()
+ .putString(KEY_MIGRATION_SOURCE_SIZE, srcX + "," + srcY)
+ .putStringSet(KEY_MIGRATION_WIDGET_MINSIZE, widgets)
+ .apply();
+ }
+
+ public static boolean shouldRunTask(Context context) {
+ return !TextUtils.isEmpty(prefs(context).getString(KEY_MIGRATION_SOURCE_SIZE, ""));
+ }
+
+ public static void clearFlags(Context context) {
+ prefs(context).edit().remove(KEY_MIGRATION_SOURCE_SIZE)
+ .remove(KEY_MIGRATION_WIDGET_MINSIZE).commit();
+ }
+
+ private static SharedPreferences prefs(Context context) {
+ return context.getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
+ Context.MODE_PRIVATE);
+ }
+}
diff --git a/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
index 61e895283..b99056023 100644
--- a/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
+++ b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
@@ -1,13 +1,14 @@
package com.android.launcher3.model;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ComponentKey;
import java.text.Collator;
import java.util.Comparator;
@@ -16,53 +17,81 @@ import java.util.HashMap;
public class WidgetsAndShortcutNameComparator implements Comparator<Object> {
private final AppWidgetManagerCompat mManager;
private final PackageManager mPackageManager;
- private final HashMap<Object, String> mLabelCache;
+ private final HashMap<ComponentKey, String> mLabelCache;
private final Collator mCollator;
private final UserHandleCompat mMainHandle;
public WidgetsAndShortcutNameComparator(Context context) {
mManager = AppWidgetManagerCompat.getInstance(context);
mPackageManager = context.getPackageManager();
- mLabelCache = new HashMap<Object, String>();
+ mLabelCache = new HashMap<>();
mCollator = Collator.getInstance();
mMainHandle = UserHandleCompat.myUserHandle();
}
- @Override
- public final int compare(Object a, Object b) {
- String labelA, labelB;
- if (mLabelCache.containsKey(a)) {
- labelA = mLabelCache.get(a);
- } else {
- labelA = (a instanceof LauncherAppWidgetProviderInfo)
- ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) a))
- : Utilities.trim(((ResolveInfo) a).loadLabel(mPackageManager));
- mLabelCache.put(a, labelA);
- }
- if (mLabelCache.containsKey(b)) {
- labelB = mLabelCache.get(b);
- } else {
- labelB = (b instanceof LauncherAppWidgetProviderInfo)
- ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) b))
- : Utilities.trim(((ResolveInfo) b).loadLabel(mPackageManager));
- mLabelCache.put(b, labelB);
- }
-
- // Currently, there is no work profile shortcuts, hence only considering the widget cases.
+ /**
+ * Resets any stored state.
+ */
+ public void reset() {
+ mLabelCache.clear();
+ }
- boolean aWorkProfile = (a instanceof LauncherAppWidgetProviderInfo) &&
- !mMainHandle.equals(mManager.getUser((LauncherAppWidgetProviderInfo) a));
- boolean bWorkProfile = (b instanceof LauncherAppWidgetProviderInfo) &&
- !mMainHandle.equals(mManager.getUser((LauncherAppWidgetProviderInfo) b));
+ @Override
+ public final int compare(Object objA, Object objB) {
+ ComponentKey keyA = getComponentKey(objA);
+ ComponentKey keyB = getComponentKey(objB);
// Independent of how the labels compare, if only one of the two widget info belongs to
// work profile, put that one in the back.
+ boolean aWorkProfile = !mMainHandle.equals(keyA.user);
+ boolean bWorkProfile = !mMainHandle.equals(keyB.user);
if (aWorkProfile && !bWorkProfile) {
return 1;
}
if (!aWorkProfile && bWorkProfile) {
return -1;
}
+
+ // Get the labels for comparison
+ String labelA = mLabelCache.get(keyA);
+ String labelB = mLabelCache.get(keyB);
+ if (labelA == null) {
+ labelA = getLabel(objA);
+ mLabelCache.put(keyA, labelA);
+ }
+ if (labelB == null) {
+ labelB = getLabel(objB);
+ mLabelCache.put(keyB, labelB);
+ }
return mCollator.compare(labelA, labelB);
}
+
+ /**
+ * @return a component key for the given widget or shortcut info.
+ */
+ private ComponentKey getComponentKey(Object o) {
+ if (o instanceof LauncherAppWidgetProviderInfo) {
+ LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
+ return new ComponentKey(widgetInfo.provider, mManager.getUser(widgetInfo));
+ } else {
+ ResolveInfo shortcutInfo = (ResolveInfo) o;
+ ComponentName cn = new ComponentName(shortcutInfo.activityInfo.packageName,
+ shortcutInfo.activityInfo.name);
+ // Currently, there are no work profile shortcuts
+ return new ComponentKey(cn, UserHandleCompat.myUserHandle());
+ }
+ }
+
+ /**
+ * @return the label for the given widget or shortcut info. This may be an expensive call.
+ */
+ private String getLabel(Object o) {
+ if (o instanceof LauncherAppWidgetProviderInfo) {
+ LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
+ return Utilities.trim(mManager.loadLabel(widgetInfo));
+ } else {
+ ResolveInfo shortcutInfo = (ResolveInfo) o;
+ return Utilities.trim(shortcutInfo.loadLabel(mPackageManager));
+ }
+ }
};
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 185dfcae3..eef4f9173 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -8,6 +8,9 @@ import android.util.Log;
import com.android.launcher3.AppFilter;
import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AlphabeticIndexCompat;
@@ -39,8 +42,8 @@ public class WidgetsModel {
private ArrayList<Object> mRawList;
private final AppWidgetManagerCompat mAppWidgetMgr;
- private final Comparator mWidgetAndShortcutNameComparator;
- private final Comparator mAppNameComparator;
+ private final WidgetsAndShortcutNameComparator mWidgetAndShortcutNameComparator;
+ private final Comparator<ItemInfo> mAppNameComparator;
private final IconCache mIconCache;
private final AppFilter mAppFilter;
private AlphabeticIndexCompat mIndexer;
@@ -54,6 +57,7 @@ public class WidgetsModel {
mIndexer = new AlphabeticIndexCompat(context);
}
+ @SuppressWarnings("unchecked")
private WidgetsModel(WidgetsModel model) {
mAppWidgetMgr = model.mAppWidgetMgr;
mPackageItemInfos = (ArrayList<PackageItemInfo>) model.mPackageItemInfos.clone();
@@ -103,6 +107,9 @@ public class WidgetsModel {
// clear the lists.
mWidgetsList.clear();
mPackageItemInfos.clear();
+ mWidgetAndShortcutNameComparator.reset();
+
+ InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
// add and update.
for (Object o: rawWidgetsShortcuts) {
@@ -111,9 +118,23 @@ public class WidgetsModel {
ComponentName componentName = null;
if (o instanceof LauncherAppWidgetProviderInfo) {
LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
- componentName = widgetInfo.provider;
- packageName = widgetInfo.provider.getPackageName();
- userHandle = mAppWidgetMgr.getUser(widgetInfo);
+
+ // Ensure that all widgets we show can be added on a workspace of this size
+ int minSpanX = Math.min(widgetInfo.spanX, widgetInfo.minSpanX);
+ int minSpanY = Math.min(widgetInfo.spanY, widgetInfo.minSpanY);
+ if (minSpanX <= (int) idp.numColumns &&
+ minSpanY <= (int) idp.numRows) {
+ componentName = widgetInfo.provider;
+ packageName = widgetInfo.provider.getPackageName();
+ userHandle = mAppWidgetMgr.getUser(widgetInfo);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, String.format(
+ "Widget %s : (%d X %d) can't fit on this device",
+ widgetInfo.provider, minSpanX, minSpanY));
+ }
+ continue;
+ }
} else if (o instanceof ResolveInfo) {
ResolveInfo resolveInfo = (ResolveInfo) o;
componentName = new ComponentName(resolveInfo.activityInfo.packageName,
@@ -139,7 +160,7 @@ public class WidgetsModel {
if (widgetsShortcutsList != null) {
widgetsShortcutsList.add(o);
} else {
- widgetsShortcutsList = new ArrayList<Object>();
+ widgetsShortcutsList = new ArrayList<>();
widgetsShortcutsList.add(o);
pInfo = new PackageItemInfo(packageName);
mIconCache.getTitleAndIconForApp(packageName, userHandle,
diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java
index 34492e4ca..8702877bf 100644
--- a/src/com/android/launcher3/testing/LauncherExtension.java
+++ b/src/com/android/launcher3/testing/LauncherExtension.java
@@ -202,6 +202,11 @@ public class LauncherExtension extends Launcher {
}
@Override
+ public boolean startSearchFromAllApps(String query) {
+ return false;
+ }
+
+ @Override
public void startVoice() {
}
diff --git a/src/com/android/launcher3/util/ComponentKey.java b/src/com/android/launcher3/util/ComponentKey.java
index 6a7df4318..b7aafaea9 100644
--- a/src/com/android/launcher3/util/ComponentKey.java
+++ b/src/com/android/launcher3/util/ComponentKey.java
@@ -64,8 +64,11 @@ public class ComponentKey {
* Encodes a component key as a string of the form [flattenedComponentString#userId].
*/
public String flattenToString(Context context) {
- return componentName.flattenToString() + "#" +
- UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+ String flattened = componentName.flattenToString();
+ if (user != null) {
+ flattened += "#" + UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+ }
+ return flattened;
}
@Override
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index b37f44719..74fc92a04 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -68,7 +68,7 @@ public class ManagedProfileHeuristic {
private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000;
public static ManagedProfileHeuristic get(Context context, UserHandleCompat user) {
- if (Utilities.isLmpOrAbove() && !UserHandleCompat.myUserHandle().equals(user)) {
+ if (Utilities.ATLEAST_LOLLIPOP && !UserHandleCompat.myUserHandle().equals(user)) {
return new ManagedProfileHeuristic(context, user);
}
return null;
@@ -296,7 +296,7 @@ public class ManagedProfileHeuristic {
* Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
*/
public static void processAllUsers(List<UserHandleCompat> users, Context context) {
- if (!Utilities.isLmpOrAbove()) {
+ if (!Utilities.ATLEAST_LOLLIPOP) {
return;
}
UserManagerCompat userManager = UserManagerCompat.getInstance(context);
diff --git a/src/com/android/launcher3/util/UiThreadCircularReveal.java b/src/com/android/launcher3/util/UiThreadCircularReveal.java
index 3ca3aeeee..f2b5e5e15 100644
--- a/src/com/android/launcher3/util/UiThreadCircularReveal.java
+++ b/src/com/android/launcher3/util/UiThreadCircularReveal.java
@@ -47,7 +47,7 @@ public class UiThreadCircularReveal {
float progress = arg0.getAnimatedFraction();
outlineProvider.setProgress(progress);
revealView.invalidateOutline();
- if (!Utilities.isLmpMR1OrAbove()) {
+ if (!Utilities.ATLEAST_LOLLIPOP_MR1) {
revealView.invalidate();
}
}
diff --git a/src/com/android/launcher3/util/WallpaperUtils.java b/src/com/android/launcher3/util/WallpaperUtils.java
index 53b2acd84..b9fccbcfd 100644
--- a/src/com/android/launcher3/util/WallpaperUtils.java
+++ b/src/com/android/launcher3/util/WallpaperUtils.java
@@ -24,6 +24,8 @@ import android.graphics.Point;
import android.os.Build;
import android.view.WindowManager;
+import com.android.launcher3.Utilities;
+
/**
* Utility methods for wallpaper management.
*/
@@ -99,7 +101,7 @@ public final class WallpaperUtils {
int maxDim = Math.max(maxDims.x, maxDims.y);
int minDim = Math.max(minDims.x, minDims.y);
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
Point realSize = new Point();
windowManager.getDefaultDisplay().getRealSize(realSize);
maxDim = Math.max(realSize.x, realSize.y);
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index 758287af3..fcb714ff1 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -31,10 +31,6 @@ import com.android.launcher3.compat.AppWidgetManagerCompat;
* @see {@link PendingAddItemInfo}
*/
public class PendingAddWidgetInfo extends PendingAddItemInfo {
- public int minWidth;
- public int minHeight;
- public int minResizeWidth;
- public int minResizeHeight;
public int previewImage;
public int icon;
public LauncherAppWidgetProviderInfo info;
@@ -50,17 +46,13 @@ public class PendingAddWidgetInfo extends PendingAddItemInfo {
this.info = i;
user = AppWidgetManagerCompat.getInstance(launcher).getUser(i);
componentName = i.provider;
- minWidth = i.minWidth;
- minHeight = i.minHeight;
- minResizeWidth = i.minResizeWidth;
- minResizeHeight = i.minResizeHeight;
previewImage = i.previewImage;
icon = i.icon;
- spanX = i.getSpanX(launcher);
- spanY = i.getSpanY(launcher);
- minSpanX = i.getMinSpanX(launcher);
- minSpanY = i.getMinSpanY(launcher);
+ spanX = i.spanX;
+ spanY = i.spanY;
+ minSpanX = i.minSpanX;
+ minSpanY = i.minSpanY;
}
public boolean isCustomWidget() {
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 7496ea2ef..94bbd929f 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -146,8 +146,8 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
mInfo = info;
// TODO(hyunyoungs): setup a cache for these labels.
mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info));
- int hSpan = Math.min(info.getSpanX(mLauncher), profile.numColumns);
- int vSpan = Math.min(info.getSpanY(mLauncher), profile.numRows);
+ int hSpan = Math.min(info.spanX, profile.numColumns);
+ int vSpan = Math.min(info.spanY, profile.numRows);
mWidgetDims.setText(String.format(mDimensionsFormatString, hSpan, vSpan));
mWidgetPreviewLoader = loader;
}
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index 30b3d581a..461aebb6b 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -15,6 +15,7 @@ import com.android.launcher3.DragLayer;
import com.android.launcher3.DragSource;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.util.Thunk;
@@ -131,7 +132,7 @@ public class WidgetHostViewLoader implements DragListener {
public static Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
Bundle options = null;
Rect rect = new Rect();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, rect);
Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher,
info.componentName, null);
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 5afd7c493..0c6ea31bb 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -319,7 +319,6 @@ public class WidgetsContainerView extends BaseContainerView
CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
ItemInfo itemInfo = (ItemInfo) d.dragInfo;
if (layout != null) {
- layout.calculateSpans(itemInfo);
showOutOfSpaceMessage =
!layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
}
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index d2ea25230..f1cde299d 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -87,6 +87,9 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
@Override
public int getItemCount() {
+ if (mWidgetsModel == null) {
+ return 0;
+ }
return mWidgetsModel.getPackageSize();
}
@@ -166,7 +169,7 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
// if the end padding is 0, then container view (horizontal scroll view) doesn't respect
// the end of the linear layout width + the start padding and doesn't allow scrolling.
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
cellList.setPaddingRelative(mIndent, 0, 1, 0);
} else {
cellList.setPadding(mIndent, 0, 1, 0);
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 61e63cdb7..884bdc418 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -64,10 +64,6 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
return Color.WHITE;
}
- public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) {
- return getResources().getColor(R.color.widgets_view_fastscroll_thumb_inactive_color);
- }
-
/**
* Sets the widget model in this view, used to determine the fast scroll position.
*/
@@ -92,6 +88,12 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
*/
@Override
public String scrollToPositionAtProgress(float touchFraction) {
+ // Skip early if widgets are not bound.
+ if (mWidgets == null) {
+ return "";
+ }
+
+ // Skip early if there are no widgets.
int rowCount = mWidgets.getPackageSize();
if (rowCount == 0) {
return "";
@@ -102,7 +104,7 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
getCurScrollState(mScrollPosState);
float pos = rowCount * touchFraction;
- int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
+ int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
@@ -115,36 +117,44 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
* Updates the bounds for the scrollbar.
*/
@Override
- public void onUpdateScrollbar() {
- int rowCount = mWidgets.getPackageSize();
+ public void onUpdateScrollbar(int dy) {
+ // Skip early if widgets are not bound.
+ if (mWidgets == null) {
+ return;
+ }
- // Skip early if, there are no items.
+ // Skip early if there are no widgets.
+ int rowCount = mWidgets.getPackageSize();
if (rowCount == 0) {
- mScrollbar.setScrollbarThumbOffset(-1, -1);
+ mScrollbar.setThumbOffset(-1, -1);
return;
}
// Skip early if, there no child laid out in the container.
getCurScrollState(mScrollPosState);
if (mScrollPosState.rowIndex < 0) {
- mScrollbar.setScrollbarThumbOffset(-1, -1);
+ mScrollbar.setThumbOffset(-1, -1);
return;
}
- synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
+ synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
}
/**
* Returns the current scroll state.
*/
- private void getCurScrollState(ScrollPositionState stateOut) {
+ protected void getCurScrollState(ScrollPositionState stateOut) {
stateOut.rowIndex = -1;
stateOut.rowTopOffset = -1;
stateOut.rowHeight = -1;
- int rowCount = mWidgets.getPackageSize();
+ // Skip early if widgets are not bound.
+ if (mWidgets == null) {
+ return;
+ }
// Return early if there are no items
+ int rowCount = mWidgets.getPackageSize();
if (rowCount == 0) {
return;
}