Merge "Ensure that setting a wallpaper snaps to a page." into ub-launcher3-master
diff --git a/res/layout/all_apps_search_market.xml b/res/layout/all_apps_search_market.xml
index 1ed5088..a2bb2e7 100644
--- a/res/layout/all_apps_search_market.xml
+++ b/res/layout/all_apps_search_market.xml
@@ -25,5 +25,5 @@
android:textSize="14sp"
android:textColor="@color/launcher_accent_color"
android:textAllCaps="true"
- android:focusable="false"
+ android:focusable="true"
android:background="@drawable/all_apps_search_market_bg" />
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 700bf9e..b8b4144 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -2685,6 +2685,7 @@
LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.dropped = true;
child.requestLayout();
+ markCellsAsOccupiedForView(child);
}
}
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 9a9b57a..76e14f5 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -19,7 +19,6 @@
import android.animation.TimeInterpolator;
import android.content.Context;
import android.graphics.PointF;
-import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AnimationUtils;
@@ -71,8 +70,10 @@
* @return true if the item was removed.
*/
public static boolean removeWorkspaceOrFolderItem(Launcher launcher, ItemInfo item, View view) {
- // Remove the item from launcher and the db
- launcher.removeItem(view, item, true /* deleteFromDb */);
+ // Remove the item from launcher and the db, we can ignore the containerInfo in this call
+ // because we already remove the drag view from the folder (if the drag originated from
+ // a folder) in Folder.beginDrag()
+ launcher.removeItem(view, null, item, true /* deleteFromDb */);
launcher.getWorkspace().stripEmptyScreens();
return true;
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 66cf2df..69ef826 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -372,7 +372,7 @@
* When {@code true}, hotseat is on the bottom row when in landscape mode.
* If {@code false}, hotseat is on the right column when in landscape mode.
*/
- boolean isVerticalBarLayout() {
+ public boolean isVerticalBarLayout() {
return isLandscape && transposeLayoutWithOrientation;
}
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 6f34587..be4d04f 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -73,7 +73,6 @@
KeyEvent.keyCodeToString(keyCode)));
}
-
if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
if (ProviderConfig.IS_DOGFOOD_BUILD) {
throw new IllegalStateException("Parent of the focused item is not supported.");
@@ -196,10 +195,11 @@
}
// Initialize the variables.
+ final Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
final ShortcutAndWidgetContainer hotseatParent = (ShortcutAndWidgetContainer) v.getParent();
final CellLayout hotseatLayout = (CellLayout) hotseatParent.getParent();
- Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
+ final ItemInfo itemInfo = (ItemInfo) v.getTag();
int pageIndex = workspace.getNextPage();
int pageCount = workspace.getChildCount();
int countX = -1;
@@ -241,9 +241,14 @@
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
profile.isVerticalBarLayout()) {
keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
- } else if (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) {
- ItemInfo info = (ItemInfo) v.getTag();
- launcher.removeItem(v, info, true /* deleteFromDb */);
+ } else if (isUninstallKeyChord(e)) {
+ matrix = FocusLogic.createSparseMatrix(iconLayout);
+ if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
+ UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
+ }
+ } else if (isDeleteKeyChord(e)) {
+ matrix = FocusLogic.createSparseMatrix(iconLayout);
+ launcher.removeItem(v, null, itemInfo, true /* deleteFromDb */);
} else {
// For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
// matrix extended with hotseat.
@@ -305,6 +310,7 @@
final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.search_drop_target_bar);
final Hotseat hotseat = (Hotseat) dragLayer.findViewById(R.id.hotseat);
+ final ItemInfo itemInfo = (ItemInfo) v.getTag();
final int iconIndex = parent.indexOfChild(v);
final int pageIndex = workspace.indexOfChild(iconLayout);
final int pageCount = workspace.getChildCount();
@@ -329,10 +335,14 @@
profile.inv.hotseatAllAppsRank,
!hotseat.hasIcons() /* ignore all apps icon, unless there are no other icons */);
countX = countX + 1;
- } else if (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) {
- ItemInfo info = (ItemInfo) v.getTag();
- launcher.removeItem(v, info, true /* deleteFromDb */);
- return consume;
+ } else if (isUninstallKeyChord(e)) {
+ matrix = FocusLogic.createSparseMatrix(iconLayout);
+ if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
+ UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
+ }
+ } else if (isDeleteKeyChord(e)) {
+ matrix = FocusLogic.createSparseMatrix(iconLayout);
+ launcher.removeItem(v, null, itemInfo, true /* deleteFromDb */);
} else {
matrix = FocusLogic.createSparseMatrix(iconLayout);
}
@@ -462,4 +472,22 @@
break;
}
}
+
+ /**
+ * Returns whether the key event represents a valid uninstall key chord.
+ */
+ private static boolean isUninstallKeyChord(KeyEvent event) {
+ int keyCode = event.getKeyCode();
+ return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
+ event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON);
+ }
+
+ /**
+ * Returns whether the key event represents a valid delete key chord.
+ */
+ private static boolean isDeleteKeyChord(KeyEvent event) {
+ int keyCode = event.getKeyCode();
+ return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
+ event.hasModifiers(KeyEvent.META_CTRL_ON);
+ }
}
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index ff21be2..ff65d30 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -1123,26 +1123,29 @@
public void run() {
CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, mInfo.screenId);
- View child = null;
- // Move the item from the folder to the workspace, in the position of the folder
- if (getItemCount() == 1) {
- ShortcutInfo finalItem = mInfo.contents.get(0);
- child = mLauncher.createShortcut(cellLayout, finalItem);
- LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container,
- mInfo.screenId, mInfo.cellX, mInfo.cellY);
- }
+ // Remove the folder
if (getItemCount() <= 1) {
- mLauncher.removeItem(mFolderIcon, mInfo, true /* deleteFromDb */);
+ mLauncher.removeItem(mFolderIcon, null, mInfo, true /* deleteFromDb */);
if (mFolderIcon instanceof DropTarget) {
mDragController.removeDropTarget((DropTarget) mFolderIcon);
}
}
- // We add the child after removing the folder to prevent both from existing at
- // the same time in the CellLayout. We need to add the new item with addInScreenFromBind()
- // to ensure that hotseat items are placed correctly.
- if (child != null) {
+
+ // Move the item from the folder to the workspace, in the position of the folder
+ if (getItemCount() == 1) {
+ ShortcutInfo finalItem = mInfo.contents.get(0);
+ View child = mLauncher.createShortcut(cellLayout, finalItem);
+ LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container,
+ mInfo.screenId, mInfo.cellX, mInfo.cellY);
+
+ // We add the child after removing the folder to prevent both from existing at
+ // the same time in the CellLayout. We need to add the new item with addInScreenFromBind()
+ // to ensure that hotseat items are placed correctly.
mLauncher.getWorkspace().addInScreenFromBind(child, mInfo.container, mInfo.screenId,
mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY);
+
+ // Focus the newly created child
+ child.requestFocus();
}
}
};
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index f5b7c56..006ce5d 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -20,7 +20,7 @@
import android.content.Context;
import android.util.AttributeSet;
-public class InfoDropTarget extends ButtonDropTarget {
+public class InfoDropTarget extends UninstallDropTarget {
public InfoDropTarget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -39,7 +39,10 @@
setDrawable(R.drawable.ic_info_launcher);
}
- public static void startDetailsActivityForInfo(ItemInfo info, Launcher launcher) {
+ /**
+ * @return Whether the activity was started.
+ */
+ public static boolean startDetailsActivityForInfo(ItemInfo info, Launcher launcher) {
ComponentName componentName = null;
if (info instanceof AppInfo) {
componentName = ((AppInfo) info).componentName;
@@ -50,7 +53,14 @@
}
if (componentName != null) {
launcher.startApplicationDetailsActivity(componentName, info.user);
+ return true;
}
+ return false;
+ }
+
+ @Override
+ protected boolean startActivityWithUninstallAffordance(DragObject d) {
+ return startDetailsActivityForInfo(d.dragInfo, mLauncher);
}
@Override
@@ -62,9 +72,4 @@
return info instanceof AppInfo || info instanceof ShortcutInfo
|| info instanceof PendingAddItemInfo;
}
-
- @Override
- void completeDrop(DragObject d) {
- startDetailsActivityForInfo(d.dragInfo, mLauncher);
- }
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7722f6e..6df7cd8 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -110,6 +110,7 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetHostViewLoader;
import com.android.launcher3.widget.WidgetsContainerView;
@@ -275,6 +276,7 @@
private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
+ private ViewOnDrawExecutor mPendingExecutor;
private LauncherModel mModel;
private IconCache mIconCache;
@@ -953,12 +955,6 @@
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;
@@ -2333,13 +2329,22 @@
}
/**
- * Unbinds the view for the specified item, and removes the item and all its children items
- * from the database. For folders, this incl udes the folder contents. AppWidgets will also
- * have their widget ids deleted.
+ * Unbinds the view for the specified item, and removes the item and all its children.
+ *
+ * @param v the view being removed.
+ * @param containerInfo the {@link FolderInfo} container of this view (can be null).
+ * @param itemInfo the {@link ItemInfo} for this view.
+ * @param deleteFromDb whether or not to delete this item from the db.
*/
- public boolean removeItem(View v, ItemInfo itemInfo, boolean deleteFromDb) {
+ public boolean removeItem(View v, FolderInfo containerInfo, ItemInfo itemInfo,
+ boolean deleteFromDb) {
if (itemInfo instanceof ShortcutInfo) {
- mWorkspace.removeWorkspaceItem(v);
+ // Remove the shortcut from the folder before removing it from launcher
+ if (containerInfo != null) {
+ containerInfo.remove((ShortcutInfo) itemInfo);
+ } else {
+ mWorkspace.removeWorkspaceItem(v);
+ }
if (deleteFromDb) {
LauncherModel.deleteItemFromDatabase(this, itemInfo);
}
@@ -3639,6 +3644,19 @@
}
/**
+ * Clear any pending bind callbacks. This is called when is loader is planning to
+ * perform a full rebind from scratch.
+ */
+ @Override
+ public void clearPendingBinds() {
+ mBindOnResumeCallbacks.clear();
+ if (mPendingExecutor != null) {
+ mPendingExecutor.markCompleted();
+ mPendingExecutor = null;
+ }
+ }
+
+ /**
* Refreshes the shortcuts shown on the workspace.
*
* Implementation of the method from LauncherModel.Callbacks.
@@ -3646,11 +3664,6 @@
public void startBinding() {
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();
-
// Clear the workspace because it's going to be rebound
mWorkspace.clearDropTargets();
mWorkspace.removeAllWorkspaceScreens();
@@ -4015,6 +4028,21 @@
mSynchronouslyBoundPages.add(page);
}
+ @Override
+ public void executeOnNextDraw(ViewOnDrawExecutor executor) {
+ if (mPendingExecutor != null) {
+ mPendingExecutor.markCompleted();
+ }
+ mPendingExecutor = executor;
+ executor.attachTo(this);
+ }
+
+ public void clearPendingExecutor(ViewOnDrawExecutor executor) {
+ if (mPendingExecutor == executor) {
+ mPendingExecutor = null;
+ }
+ }
+
/**
* Callback saying that there aren't any more items to bind.
*
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 323794f..0f043ce 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -64,6 +64,7 @@
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.ManagedProfileHeuristic;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.ViewOnDrawExecutor;
import java.lang.ref.WeakReference;
import java.net.URISyntaxException;
@@ -79,6 +80,7 @@
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.Executor;
/**
* Maintains in-memory state of the Launcher. It is expected that there should be only one
@@ -124,12 +126,6 @@
@Thunk boolean mWorkspaceLoaded;
@Thunk boolean mAllAppsLoaded;
- // When we are loading pages synchronously, we can't just post the binding of items on the side
- // pages as this delays the rotation process. Instead, we wait for a callback from the first
- // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
- // a normal load, we also clear this set of Runnables.
- static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
-
/**
* Set of runnables to be called on the background thread after the workspace binding
* is complete.
@@ -171,6 +167,9 @@
// sBgWidgetProviders is the set of widget providers including custom internal widgets
public static HashMap<ComponentKey, LauncherAppWidgetProviderInfo> sBgWidgetProviders;
+ // sBgShortcutProviders is the set of custom shortcut providers
+ public static List<ResolveInfo> sBgShortcutProviders;
+
// sPendingPackages is a set of packages which could be on sdcard and are not available yet
static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
new HashMap<UserHandleCompat, HashSet<String>>();
@@ -185,6 +184,7 @@
public interface Callbacks {
public boolean setLoadOnResume();
public int getCurrentWorkspaceScreen();
+ public void clearPendingBinds();
public void startBinding();
public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
boolean forceAnimateIcons);
@@ -209,6 +209,7 @@
public void bindSearchProviderChanged();
public boolean isAllAppsButtonRank(int rank);
public void onPageBoundSynchronously(int page);
+ public void executeOnNextDraw(ViewOnDrawExecutor executor);
public void dumpLogsToLocalData();
}
@@ -587,11 +588,6 @@
"main thread");
}
- // Clear any deferred bind runnables
- synchronized (mDeferredBindRunnables) {
- mDeferredBindRunnables.clear();
- }
-
// Remove any queued UI runnables
mHandler.cancelAll();
// Unbind all the workspace items
@@ -1339,14 +1335,16 @@
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
InstallShortcutReceiver.enableInstallQueue();
synchronized (mLock) {
- // Clear any deferred bind-runnables from the synchronized load process
- // We must do this before any loading/binding is scheduled below.
- synchronized (mDeferredBindRunnables) {
- mDeferredBindRunnables.clear();
- }
-
// Don't bother to start the thread if we know it's not going to do anything
if (mCallbacks != null && mCallbacks.get() != null) {
+ final Callbacks oldCallbacks = mCallbacks.get();
+ // Clear any pending bind-runnables from the synchronized load process.
+ runOnMainThread(new Runnable() {
+ public void run() {
+ oldCallbacks.clearPendingBinds();
+ }
+ });
+
// If there is already one running, tell it to stop.
stopLoaderLocked();
mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
@@ -1361,21 +1359,6 @@
}
}
- void bindRemainingSynchronousPages() {
- // Post the remaining side pages to be loaded
- if (!mDeferredBindRunnables.isEmpty()) {
- Runnable[] deferredBindRunnables = null;
- synchronized (mDeferredBindRunnables) {
- deferredBindRunnables = mDeferredBindRunnables.toArray(
- new Runnable[mDeferredBindRunnables.size()]);
- mDeferredBindRunnables.clear();
- }
- for (final Runnable r : deferredBindRunnables) {
- mHandler.post(r);
- }
- }
- }
-
public void stopLoader() {
synchronized (mLock) {
if (mLoaderTask != null) {
@@ -2501,9 +2484,7 @@
final ArrayList<ItemInfo> workspaceItems,
final ArrayList<LauncherAppWidgetInfo> appWidgets,
final LongArrayMap<FolderInfo> folders,
- ArrayList<Runnable> deferredBindRunnables) {
-
- final boolean postOnMainThread = (deferredBindRunnables != null);
+ final Executor executor) {
// Bind the workspace items
int N = workspaceItems.size();
@@ -2520,13 +2501,7 @@
}
}
};
- if (postOnMainThread) {
- synchronized (deferredBindRunnables) {
- deferredBindRunnables.add(r);
- }
- } else {
- runOnMainThread(r);
- }
+ executor.execute(r);
}
// Bind the folders
@@ -2539,13 +2514,7 @@
}
}
};
- if (postOnMainThread) {
- synchronized (deferredBindRunnables) {
- deferredBindRunnables.add(r);
- }
- } else {
- runOnMainThread(r);
- }
+ executor.execute(r);
}
// Bind the widgets, one at a time
@@ -2560,11 +2529,7 @@
}
}
};
- if (postOnMainThread) {
- deferredBindRunnables.add(r);
- } else {
- runOnMainThread(r);
- }
+ executor.execute(r);
}
}
@@ -2642,6 +2607,7 @@
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
+ callbacks.clearPendingBinds();
callbacks.startBinding();
}
}
@@ -2650,28 +2616,20 @@
bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
- // Load items on the current page
+ Executor mainExecutor = new DeferredMainThreadExecutor();
+ // Load items on the current page.
bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
- currentFolders, null);
- if (isLoadingSynchronously) {
- r = new Runnable() {
- public void run() {
- Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
- callbacks.onPageBoundSynchronously(currentScreen);
- }
- }
- };
- runOnMainThread(r);
- }
+ currentFolders, mainExecutor);
- // Load all the remaining pages (if we are loading synchronously, we want to defer this
- // work until after the first render)
- synchronized (mDeferredBindRunnables) {
- mDeferredBindRunnables.clear();
- }
- bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
- (isLoadingSynchronously ? mDeferredBindRunnables : null));
+ // In case of isLoadingSynchronously, only bind the first screen, and defer binding the
+ // remaining screens after first onDraw is called. This ensures that the first screen
+ // is immediately visible (eg. during rotation)
+ // In case of !isLoadingSynchronously, bind all pages one after other.
+ final Executor deferredExecutor = isLoadingSynchronously ?
+ new ViewOnDrawExecutor(mHandler) : mainExecutor;
+
+ bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets,
+ otherFolders, deferredExecutor);
// Tell the workspace that we're done binding items
r = new Runnable() {
@@ -2701,11 +2659,23 @@
}
};
+ deferredExecutor.execute(r);
+
if (isLoadingSynchronously) {
- synchronized (mDeferredBindRunnables) {
- mDeferredBindRunnables.add(r);
- }
- } else {
+ r = new Runnable() {
+ public void run() {
+ Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+ if (callbacks != null) {
+ // We are loading synchronously, which means, some of the pages will be
+ // bound after first draw. Inform the callbacks that page binding is
+ // not complete, and schedule the remaining pages.
+ if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
+ callbacks.onPageBoundSynchronously(currentScreen);
+ }
+ callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
+ }
+ }
+ };
runOnMainThread(r);
}
}
@@ -3315,10 +3285,14 @@
PackageManager pm = context.getPackageManager();
for (String pkg : mPackages) {
try {
- needToRefresh |= !pm.queryBroadcastReceivers(
+ List<ResolveInfo> widgets = pm.queryBroadcastReceivers(
new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
- .setPackage(pkg), 0).isEmpty();
+ .setPackage(pkg), 0);
+ needToRefresh |= widgets != null && !widgets.isEmpty();
} catch (RuntimeException e) {
+ if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ throw e;
+ }
// Ignore the crash. We can live with a state widget list.
Log.e(TAG, "PM call failed for " + pkg, e);
}
@@ -3373,8 +3347,9 @@
return results;
}
} catch (Exception e) {
- if (e.getCause() instanceof TransactionTooLargeException ||
- e.getCause() instanceof DeadObjectException) {
+ if (!ProviderConfig.IS_DOGFOOD_BUILD &&
+ (e.getCause() instanceof TransactionTooLargeException ||
+ e.getCause() instanceof DeadObjectException)) {
// the returned value may be incomplete and will not be refreshed until the next
// time Launcher starts.
// TODO: after figuring out a repro step, introduce a dirty bit to check when
@@ -3434,8 +3409,29 @@
PackageManager packageManager = mApp.getContext().getPackageManager();
final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
widgetsAndShortcuts.addAll(getWidgetProviders(mApp.getContext(), refresh));
- Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
- widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
+
+ // Update shortcut providers
+ synchronized (sBgLock) {
+ try {
+ Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+ List<ResolveInfo> providers = packageManager.queryIntentActivities(shortcutsIntent, 0);
+ sBgShortcutProviders = providers;
+ } catch (RuntimeException e) {
+ if (!ProviderConfig.IS_DOGFOOD_BUILD &&
+ (e.getCause() instanceof TransactionTooLargeException ||
+ e.getCause() instanceof DeadObjectException)) {
+ /**
+ * Ignore exception and use the cached list if available.
+ * Refer to {@link #getWidgetProviders(Context, boolean}} for more info.
+ */
+ } else {
+ throw e;
+ }
+ }
+ if (sBgShortcutProviders != null) {
+ widgetsAndShortcuts.addAll(sBgShortcutProviders);
+ }
+ }
mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
}
@@ -3737,6 +3733,14 @@
}
}
+ @Thunk class DeferredMainThreadExecutor implements Executor {
+
+ @Override
+ public void execute(Runnable command) {
+ runOnMainThread(command);
+ }
+ }
+
/**
* @return the looper for the worker thread which can be used to start background tasks.
*/
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 0e20fab..d3af19a 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -176,7 +176,7 @@
}
/**
- * Starts and animation to the workspace from the current overlay view.
+ * Starts an animation to the workspace from the current overlay view.
*/
public void startAnimationToWorkspace(final Launcher.State fromState,
final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
@@ -190,9 +190,13 @@
if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
animated, onCompleteRunnable);
- } else {
+ } else if (fromState == Launcher.State.WIDGETS ||
+ fromState == Launcher.State.WIDGETS_SPRING_LOADED) {
startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
animated, onCompleteRunnable);
+ } else {
+ startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState,
+ animated, onCompleteRunnable);
}
}
@@ -403,7 +407,7 @@
}
/**
- * Starts and animation to the workspace from the apps view.
+ * Starts an animation to the workspace from the apps view.
*/
private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
final Workspace.State toWorkspaceState, final boolean animated,
@@ -448,10 +452,10 @@
}
/**
- * Starts and animation to the workspace from the widgets view.
+ * Starts an animation to the workspace from the widgets view.
*/
private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
- final Workspace.State toWorkspaceState, final boolean animated,
+ final Workspace.State toWorkspaceState, final boolean animated,
final Runnable onCompleteRunnable) {
final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
@@ -477,6 +481,95 @@
}
/**
+ * Starts an animation to the workspace from another workspace state, e.g. normal to overview.
+ */
+ private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState,
+ final Workspace.State toWorkspaceState, final boolean animated,
+ final Runnable onCompleteRunnable) {
+ final View fromWorkspace = mLauncher.getWorkspace();
+ final HashMap<View, Integer> layerViews = new HashMap<>();
+ final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
+ final int revealDuration = mLauncher.getResources()
+ .getInteger(R.integer.config_overlayRevealTime);
+
+ // Cancel the current animation
+ cancelAnimation();
+
+ // Create the workspace animation.
+ // NOTE: this call apparently also sets the state for the workspace if !animated
+ Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
+ animated, layerViews);
+
+ startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState,
+ animated ? revealDuration : 0, null);
+
+ if (animated) {
+ if (workspaceAnim != null) {
+ animation.play(workspaceAnim);
+ }
+ dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, true);
+
+ final AnimatorSet stateAnimation = animation;
+ final Runnable startAnimRunnable = new Runnable() {
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public void run() {
+ // Check that mCurrentAnimation hasn't changed while
+ // we waited for a layout/draw pass
+ if (mCurrentAnimation != stateAnimation)
+ return;
+
+ dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
+
+ // Enable all necessary layers
+ for (View v : layerViews.keySet()) {
+ if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
+ v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+ if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
+ v.buildLayer();
+ }
+ }
+ stateAnimation.start();
+ }
+ };
+ animation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
+
+ // Run any queued runnables
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+
+ // Disable all necessary layers
+ for (View v : layerViews.keySet()) {
+ if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
+ v.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+
+ // This can hold unnecessary references to views.
+ cleanupAnimation();
+ }
+ });
+ fromWorkspace.post(startAnimRunnable);
+ mCurrentAnimation = animation;
+ } else /* if (!animated) */ {
+ dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, true);
+ dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
+ dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
+
+ // Run any queued runnables
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+
+ mCurrentAnimation = null;
+ }
+ }
+
+ /**
* Creates and starts a new animation to the workspace.
*/
private AnimatorSet startAnimationToWorkspaceFromOverlay(final Workspace.State fromWorkspaceState,
@@ -683,7 +776,7 @@
fromView.post(startAnimRunnable);
return animation;
- } else {
+ } else /* if (!(animated && initialized)) */ {
fromView.setVisibility(View.GONE);
dispatchOnLauncherTransitionPrepare(fromView, animated, true);
dispatchOnLauncherTransitionStart(fromView, animated, true);
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 9ed4fb6..b69c79d 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -82,7 +82,7 @@
void completeDrop(final DragObject d) {
final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(d.dragInfo);
final UserHandleCompat user = d.dragInfo.user;
- if (startUninstallActivity(mLauncher, d.dragInfo)) {
+ if (startActivityWithUninstallAffordance(d)) {
final Runnable checkIfUninstallWasSuccess = new Runnable() {
@Override
@@ -99,6 +99,10 @@
}
}
+ protected boolean startActivityWithUninstallAffordance(DragObject d) {
+ return startUninstallActivity(mLauncher, d.dragInfo);
+ }
+
public static boolean startUninstallActivity(Launcher launcher, ItemInfo info) {
final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info);
final UserHandleCompat user = info.user;
@@ -115,7 +119,7 @@
/**
* Interface defining an object that can provide uninstallable drag objects.
*/
- public static interface UninstallSource {
+ public interface UninstallSource {
/**
* A pending uninstall operation was complete.
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 0fe37de..778bff7 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -278,13 +278,6 @@
private AccessibilityDelegate mPagesAccessibilityDelegate;
- private final Runnable mBindPages = new Runnable() {
- @Override
- public void run() {
- mLauncher.getModel().bindRemainingSynchronousPages();
- }
- };
-
/**
* Used to inflate the Workspace from XML.
*
@@ -1112,7 +1105,7 @@
if (lahv != null && lahv.isReinflateRequired()) {
// Remove and rebind the current widget (which was inflated in the wrong
// orientation), but don't delete it from the database
- mLauncher.removeItem(lahv, info, false /* deleteFromDb */);
+ mLauncher.removeItem(lahv, null, info, false /* deleteFromDb */);
mLauncher.bindAppWidget(info);
}
}
@@ -1720,14 +1713,6 @@
}
@Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- // Call back to LauncherModel to finish binding after the first draw
- post(mBindPages);
- }
-
- @Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
if (!mLauncher.isAppsViewVisible()) {
final Folder openFolder = getOpenFolder();
@@ -4456,7 +4441,7 @@
if (info.hostView instanceof PendingAppWidgetHostView) {
// Remove and rebind the current widget, but don't delete it from the database
PendingAppWidgetHostView view = (PendingAppWidgetHostView) info.hostView;
- mLauncher.removeItem(view, info, false /* deleteFromDb */);
+ mLauncher.removeItem(view, null, info, false /* deleteFromDb */);
mLauncher.bindAppWidget(info);
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
index 117aca9..dafa73f 100644
--- a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
+++ b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
@@ -35,6 +35,7 @@
private float mXPercent;
private float mYPercent;
private int mGravity;
+ private int mAlpha;
/**
* @param gravity If one of the Gravity center values, the x and y offset will take the width
@@ -50,10 +51,11 @@
public void setAlpha(int alpha) {
mImage.setAlpha(alpha);
+ mAlpha = alpha;
}
public int getAlpha() {
- return mImage.getAlpha();
+ return mAlpha;
}
public void updateBounds(Rect bounds) {
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index e72f341..4a39e6b 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -42,7 +42,6 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.PagedView;
import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Utilities;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.R;
@@ -657,21 +656,29 @@
ViewConfiguration config = ViewConfiguration.get(mLauncher);
mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
-
+ PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+ float theta = MAX_FLING_DEGREES + 1;
if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
// Do a quick dot product test to ensure that we are flinging upwards
- PointF vel = new PointF(mVelocityTracker.getXVelocity(),
- mVelocityTracker.getYVelocity());
PointF upVec = new PointF(0f, -1f);
- float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
- (vel.length() * upVec.length()));
- if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
- return vel;
- }
+ theta = getAngleBetweenVectors(vel, upVec);
+ } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() &&
+ mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) {
+ // Remove icon is on left side instead of top, so check if we are flinging to the left.
+ PointF leftVec = new PointF(-1f, 0f);
+ theta = getAngleBetweenVectors(vel, leftVec);
+ }
+ if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
+ return vel;
}
return null;
}
+ private float getAngleBetweenVectors(PointF vec1, PointF vec2) {
+ return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) /
+ (vec1.length() * vec2.length()));
+ }
+
void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
final int[] coordinates = mCoordinatesTemp;
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index f82038b..da8bae7 100644
--- a/src/com/android/launcher3/util/FlingAnimation.java
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -51,7 +51,7 @@
mFrom.top += yOffset;
mFrom.bottom -= yOffset;
- mDuration = initDuration();
+ mDuration = Math.abs(vel.y) > Math.abs(vel.x) ? initFlingUpDuration() : initFlingLeftDuration();
mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY);
}
@@ -62,7 +62,7 @@
* - Calculate a constant acceleration in x direction such that the object reaches
* {@link #mIconRect} in the given time.
*/
- protected int initDuration() {
+ protected int initFlingUpDuration() {
float sY = -mFrom.bottom;
float d = mUY * mUY + 2 * sY * MAX_ACCELERATION;
@@ -83,6 +83,34 @@
return (int) Math.round(t);
}
+ /**
+ * The fling animation is based on the following system
+ * - Apply a constant force in the x direction to causing the fling to decelerate.
+ * - The animation runs for the time taken by the object to go out of the screen.
+ * - Calculate a constant acceleration in y direction such that the object reaches
+ * {@link #mIconRect} in the given time.
+ */
+ protected int initFlingLeftDuration() {
+ float sX = -mFrom.right;
+
+ float d = mUX * mUX + 2 * sX * MAX_ACCELERATION;
+ if (d >= 0) {
+ // sX can be reached under the MAX_ACCELERATION. Use MAX_ACCELERATION for x direction.
+ mAX = MAX_ACCELERATION;
+ } else {
+ // sX is not reachable, decrease the acceleration so that sX is almost reached.
+ d = 0;
+ mAX = mUX * mUX / (2 * -sX);
+ }
+ double t = (-mUX - Math.sqrt(d)) / mAX;
+
+ float sY = -mFrom.exactCenterY() + mIconRect.exactCenterY();
+
+ // Find vertical acceleration such that: u*t + a*t*t/2 = s
+ mAY = (float) ((sY - t * mUY) * 2 / (t * t));
+ return (int) Math.round(t);
+ }
+
public final int getDuration() {
return mDuration + DRAG_END_DELAY;
}
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
new file mode 100644
index 0000000..01808ba
--- /dev/null
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -0,0 +1,96 @@
+/**
+ * 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.util;
+
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.ViewTreeObserver.OnDrawListener;
+
+import com.android.launcher3.DeferredHandler;
+import com.android.launcher3.Launcher;
+
+import java.util.ArrayList;
+import java.util.concurrent.Executor;
+
+/**
+ * An executor which runs all the tasks after the first onDraw is called on the target view.
+ */
+public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable,
+ OnAttachStateChangeListener {
+
+ private final ArrayList<Runnable> mTasks = new ArrayList<>();
+ private final DeferredHandler mHandler;
+
+ private Launcher mLauncher;
+ private View mAttachedView;
+ private boolean mCompleted;
+
+ public ViewOnDrawExecutor(DeferredHandler handler) {
+ mHandler = handler;
+ }
+
+ public void attachTo(Launcher launcher) {
+ mLauncher = launcher;
+ mAttachedView = launcher.getWorkspace();
+ mAttachedView.addOnAttachStateChangeListener(this);
+
+ attachObserver();
+ }
+
+ private void attachObserver() {
+ if (!mCompleted) {
+ mAttachedView.getViewTreeObserver().addOnDrawListener(this);
+ }
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ mTasks.add(command);
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ attachObserver();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) { }
+
+ @Override
+ public void onDraw() {
+ mAttachedView.post(this);
+ }
+
+ @Override
+ public void run() {
+ for (final Runnable r : mTasks) {
+ mHandler.post(r);
+ }
+ markCompleted();
+ }
+
+ public void markCompleted() {
+ mTasks.clear();
+ if (mAttachedView != null) {
+ mAttachedView.getViewTreeObserver().removeOnDrawListener(this);
+ mAttachedView.removeOnAttachStateChangeListener(this);
+ }
+ if (mLauncher != null) {
+ mLauncher.clearPendingExecutor(this);
+ }
+ }
+}