diff options
33 files changed, 792 insertions, 805 deletions
diff --git a/res/layout/search_drop_target_bar.xml b/res/layout/search_drop_target_bar.xml index 9b0da1d4e..fe18edc84 100644 --- a/res/layout/search_drop_target_bar.xml +++ b/res/layout/search_drop_target_bar.xml @@ -45,26 +45,26 @@ style="@style/DropTargetButtonContainer" android:layout_weight="1" > - <!-- Uninstall target --> + <!-- Info target --> - <com.android.launcher3.UninstallDropTarget - android:id="@+id/uninstall_target_text" + <com.android.launcher3.InfoDropTarget + android:id="@+id/info_target_text" style="@style/DropTargetButton" - android:drawableStart="@drawable/uninstall_target_selector" - android:text="@string/delete_target_uninstall_label" /> + android:drawableStart="@drawable/info_target_selector" + android:text="@string/info_target_label" /> </FrameLayout> <FrameLayout style="@style/DropTargetButtonContainer" android:layout_weight="1" > - <!-- Info target --> + <!-- Uninstall target --> - <com.android.launcher3.InfoDropTarget - android:id="@+id/info_target_text" + <com.android.launcher3.UninstallDropTarget + android:id="@+id/uninstall_target_text" style="@style/DropTargetButton" - android:drawableStart="@drawable/info_target_selector" - android:text="@string/info_target_label" /> + android:drawableStart="@drawable/uninstall_target_selector" + android:text="@string/delete_target_uninstall_label" /> </FrameLayout> </LinearLayout> diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml index f53b74ef8..64ddea1ae 100644 --- a/res/layout/widget_cell.xml +++ b/res/layout/widget_cell.xml @@ -19,9 +19,11 @@ android:layout_width="@dimen/widget_preview_container_width" android:layout_height="wrap_content" android:layout_weight="1" - android:paddingTop="@dimen/widget_preview_padding_top" + android:layout_marginTop="@dimen/widget_preview_padding_top" + android:layout_marginLeft="8dp" + android:layout_marginBottom="8dp" android:orientation="vertical" - android:background="@drawable/focusable_view_bg" + android:background="@color/bubble_dark_background" android:focusable="true"> <LinearLayout @@ -45,7 +47,7 @@ android:fadingEdge="horizontal" android:textColor="#FFFFFFFF" - android:textSize="12sp" + android:textSize="16sp" android:textAlignment="viewStart" android:fontFamily="sans-serif-condensed" android:shadowRadius="2.0" @@ -61,9 +63,8 @@ android:layout_marginLeft="5dp" android:layout_weight="0" android:gravity="start" - android:textColor="#FFFFFFFF" - android:textSize="12sp" + android:textSize="16sp" android:textAlignment="viewStart" android:fontFamily="sans-serif-condensed" android:shadowRadius="2.0" diff --git a/res/layout/widgets_list_row_view.xml b/res/layout/widgets_list_row_view.xml index 017b45066..f94d02330 100644 --- a/res/layout/widgets_list_row_view.xml +++ b/res/layout/widgets_list_row_view.xml @@ -19,10 +19,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:layout_marginLeft="8dp" - android:layout_marginRight="8dp" - android:layout_marginTop="8dp" - android:layout_marginBottom="8dp" android:focusable="true" android:background="@drawable/focusable_view_bg" android:descendantFocusability="afterDescendants"> @@ -49,7 +45,6 @@ android:id="@+id/section" android:layout_width="match_parent" android:layout_height="@dimen/widget_section_height" - android:paddingTop="8dp" android:paddingLeft="16dp" android:paddingRight="16dp" android:singleLine="true" @@ -80,6 +75,7 @@ android:id="@+id/widgets_scroll_container" android:layout_width="match_parent" android:layout_height="@dimen/widget_cell_height" + android:paddingLeft="40dp" android:scrollbars="none" > <LinearLayout android:id="@+id/widgets_cell_list" diff --git a/res/values/dimens.xml b/res/values/dimens.xml index cad60fbae..da1108271 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -80,7 +80,7 @@ <!-- Widget tray --> <dimen name="widget_container_inset">8dp</dimen> - <dimen name="widget_preview_size">140dp</dimen> + <dimen name="widget_preview_size">120dp</dimen> <dimen name="widget_preview_padding_top">8dp</dimen> <dimen name="widget_preview_label_vertical_padding">8dp</dimen> <dimen name="widget_preview_label_horizontal_padding">8dp</dimen> @@ -90,8 +90,8 @@ <dimen name="widget_section_icon_padding">8dp</dimen> <!-- Equation: widget_preview_size + 2 * widget_preview_padding_horizontal --> - <dimen name="widget_preview_container_width">156dp</dimen> - <dimen name="widget_cell_height">160dp</dimen> + <dimen name="widget_preview_container_width">136dp</dimen> + <dimen name="widget_cell_height">150dp</dimen> <!-- Padding applied to shortcut previews --> <dimen name="shortcut_preview_padding_left">0dp</dimen> diff --git a/res/values/strings.xml b/res/values/strings.xml index 52306e30e..bfe7e36f2 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -309,13 +309,13 @@ s --> <!-- Strings for accessibility actions --> <!-- Accessibility action to add an app to workspace. [CHAR_LIMIT=30] [DO NOT TRANSLATE] --> - <string name="action_add_to_workspace">Add To Workspace</string> + <string name="action_add_to_workspace">Add to workspace</string> <!-- Accessibility confirmation for item added to workspace [DO NOT TRANSLATE] --> <string name="item_added_to_workspace">Item added to workspace</string> <!-- Accessibility confirmation for item removed [DO NOT TRANSLATE] --> - <string name="item_removed_from_workspace">Item removed from workspace</string> + <string name="item_removed">Item removed</string> <!-- Accessibility action to move an item on the workspace. [CHAR_LIMIT=30] [DO NOT TRANSLATE] --> <string name="action_move">Move Item</string> diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index 240250750..3c698c014 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -349,7 +349,7 @@ public class AppWidgetResizeFrame extends FrameLayout { mTmpRect.right, mTmpRect.bottom); } - static Rect getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect) { + public static Rect getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect) { if (rect == null) { rect = new Rect(); } diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java index 5b399087a..fb49df5df 100644 --- a/src/com/android/launcher3/ButtonDropTarget.java +++ b/src/com/android/launcher3/ButtonDropTarget.java @@ -26,18 +26,19 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; import android.util.AttributeSet; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; import android.widget.TextView; -import com.android.launcher3.R; import com.android.launcher3.util.Thunk; /** * Implements a DropTarget. */ -public abstract class ButtonDropTarget extends TextView implements DropTarget, DragController.DragListener { +public abstract class ButtonDropTarget extends TextView + implements DropTarget, DragController.DragListener, OnClickListener { private static int DRAG_VIEW_DROP_DURATION = 285; @@ -256,4 +257,18 @@ public abstract class ButtonDropTarget extends TextView implements DropTarget, D public void getLocationInDragLayer(int[] loc) { mLauncher.getDragLayer().getLocationInDragLayer(this, loc); } + + public void enableAccessibleDrag(boolean enable) { + setOnClickListener(enable ? this : null); + } + + protected String getAccessibilityDropConfirmation() { + return null; + } + + @Override + public void onClick(View v) { + LauncherAppState.getInstance().getAccessibilityDelegate() + .handleAccessibleDrop(this, null, getAccessibilityDropConfirmation()); + } } diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index f4afb954d..f08f25fb2 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -557,7 +557,7 @@ public class CellLayout extends ViewGroup { Resources res = getContext().getResources(); View child = getChildAt(x, y); if (child == null || child == dragInfo.item) { - return res.getString(R.string.move_to_empty_cell, x, y); + return res.getString(R.string.move_to_empty_cell, x + 1, y + 1); } else { ItemInfo info = (ItemInfo) child.getTag(); if (info instanceof AppInfo || info instanceof ShortcutInfo) { diff --git a/src/com/android/launcher3/DeferredHandler.java b/src/com/android/launcher3/DeferredHandler.java index eb7c26a28..a43ab6723 100644 --- a/src/com/android/launcher3/DeferredHandler.java +++ b/src/com/android/launcher3/DeferredHandler.java @@ -20,12 +20,10 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.MessageQueue; -import android.util.Pair; import com.android.launcher3.util.Thunk; import java.util.LinkedList; -import java.util.ListIterator; /** * Queue of things to run on a looper thread. Items posted with {@link #post} will not @@ -35,20 +33,18 @@ import java.util.ListIterator; * This class is fifo. */ public class DeferredHandler { - @Thunk LinkedList<Pair<Runnable, Integer>> mQueue = new LinkedList<Pair<Runnable, Integer>>(); + @Thunk LinkedList<Runnable> mQueue = new LinkedList<>(); private MessageQueue mMessageQueue = Looper.myQueue(); private Impl mHandler = new Impl(); @Thunk class Impl extends Handler implements MessageQueue.IdleHandler { public void handleMessage(Message msg) { - Pair<Runnable, Integer> p; Runnable r; synchronized (mQueue) { if (mQueue.size() == 0) { return; } - p = mQueue.removeFirst(); - r = p.first; + r = mQueue.removeFirst(); } r.run(); synchronized (mQueue) { @@ -79,11 +75,8 @@ public class DeferredHandler { /** Schedule runnable to run after everything that's on the queue right now. */ public void post(Runnable runnable) { - post(runnable, 0); - } - public void post(Runnable runnable, int type) { synchronized (mQueue) { - mQueue.add(new Pair<Runnable, Integer>(runnable, type)); + mQueue.add(runnable); if (mQueue.size() == 1) { scheduleNextLocked(); } @@ -92,31 +85,10 @@ public class DeferredHandler { /** Schedule runnable to run when the queue goes idle. */ public void postIdle(final Runnable runnable) { - postIdle(runnable, 0); - } - public void postIdle(final Runnable runnable, int type) { - post(new IdleRunnable(runnable), type); - } - - public void cancelRunnable(Runnable runnable) { - synchronized (mQueue) { - while (mQueue.remove(runnable)) { } - } - } - public void cancelAllRunnablesOfType(int type) { - synchronized (mQueue) { - ListIterator<Pair<Runnable, Integer>> iter = mQueue.listIterator(); - Pair<Runnable, Integer> p; - while (iter.hasNext()) { - p = iter.next(); - if (p.second == type) { - iter.remove(); - } - } - } + post(new IdleRunnable(runnable)); } - public void cancel() { + public void cancelAll() { synchronized (mQueue) { mQueue.clear(); } @@ -124,20 +96,19 @@ public class DeferredHandler { /** Runs all queued Runnables from the calling thread. */ public void flush() { - LinkedList<Pair<Runnable, Integer>> queue = new LinkedList<Pair<Runnable, Integer>>(); + LinkedList<Runnable> queue = new LinkedList<>(); synchronized (mQueue) { queue.addAll(mQueue); mQueue.clear(); } - for (Pair<Runnable, Integer> p : queue) { - p.first.run(); + for (Runnable r : queue) { + r.run(); } } void scheduleNextLocked() { if (mQueue.size() > 0) { - Pair<Runnable, Integer> p = mQueue.getFirst(); - Runnable peek = p.first; + Runnable peek = mQueue.getFirst(); if (peek instanceof IdleRunnable) { mMessageQueue.addIdleHandler(mHandler); } else { diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java index aa3e66c09..e741b9787 100644 --- a/src/com/android/launcher3/DeleteDropTarget.java +++ b/src/com/android/launcher3/DeleteDropTarget.java @@ -29,7 +29,6 @@ import android.view.ViewConfiguration; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; -import com.android.launcher3.R; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.WidgetsContainerView; @@ -59,13 +58,15 @@ public class DeleteDropTarget extends ButtonDropTarget { setDrawable(R.drawable.remove_target_selector); } - public static boolean willAcceptDrop(DragSource source, Object info) { - return (info instanceof ItemInfo) && source.supportsDeleteDropTarget(); + public static boolean supportsDrop(Object info) { + return (info instanceof ShortcutInfo) + || (info instanceof LauncherAppWidgetInfo) + || (info instanceof FolderInfo); } @Override protected boolean supportsDrop(DragSource source, Object info) { - return willAcceptDrop(source, info); + return source.supportsDeleteDropTarget() && supportsDrop(info); } @Override @@ -304,4 +305,9 @@ public class DeleteDropTarget extends ButtonDropTarget { dragLayer.animateView(d.dragView, updateCb, duration, tInterpolator, onAnimationEndRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null); } + + @Override + protected String getAccessibilityDropConfirmation() { + return getResources().getString(R.string.item_removed); + } } diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java index b24608cb1..196886895 100644 --- a/src/com/android/launcher3/DragController.java +++ b/src/com/android/launcher3/DragController.java @@ -462,8 +462,7 @@ public class DragController { mLastTouchUpTime = System.currentTimeMillis(); if (mDragging) { PointF vec = isFlingingToDelete(mDragObject.dragSource); - if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragSource, - mDragObject.dragInfo)) { + if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) { vec = null; } if (vec != null) { @@ -617,7 +616,7 @@ public class DragController { if (mDragging) { PointF vec = isFlingingToDelete(mDragObject.dragSource); - if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragSource, mDragObject.dragInfo)) { + if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) { vec = null; } if (vec != null) { diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java index c35ce944f..dff47c256 100644 --- a/src/com/android/launcher3/Folder.java +++ b/src/com/android/launcher3/Folder.java @@ -745,9 +745,18 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList replaceFolderWithFinalItem(); } } else { - rearrangeChildren(); // The drag failed, we need to return the item to the folder + ShortcutInfo info = (ShortcutInfo) d.dragInfo; + View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info) + ? mCurrentDragView : mContent.createNewView(info); + ArrayList<View> views = getItemsInReadingOrder(); + views.add(info.rank, icon); + mContent.arrangeChildren(views, views.size()); + mItemsInvalidated = true; + + mSuppressOnAdd = true; mFolderIcon.onDrop(d); + mSuppressOnAdd = false; } if (target != this) { diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java index 617489271..3f08f43c2 100644 --- a/src/com/android/launcher3/FolderPagedView.java +++ b/src/com/android/launcher3/FolderPagedView.java @@ -363,7 +363,7 @@ public class FolderPagedView extends PagedView { } @SuppressLint("InflateParams") - private View createNewView(ShortcutInfo item) { + public View createNewView(ShortcutInfo item) { final BubbleTextView textView = (BubbleTextView) mInflater.inflate( R.layout.folder_application, null, false); textView.applyFromShortcutInfo(item, mIconCache, false); diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index 48b38f182..fd4571482 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -101,7 +101,7 @@ public class IconCache { mIconDpi = activityManager.getLauncherLargeIconDensity(); mIconDb = new IconDB(context); - mWorkerHandler = new Handler(LauncherModel.sWorkerThread.getLooper()); + mWorkerHandler = new Handler(LauncherModel.getWorkerLooper()); } private Drawable getFullResDefaultActivityIcon() { @@ -388,16 +388,20 @@ public class IconCache { return new IconLoadRequest(request, mWorkerHandler); } + private Bitmap getNonNullIcon(CacheEntry entry, UserHandleCompat user) { + return entry.icon == null ? getDefaultIcon(user) : entry.icon; + } + /** * Fill in "application" with the icon and label for "info." */ public synchronized void getTitleAndIcon(AppInfo application, LauncherActivityInfoCompat info, boolean useLowResIcon) { - CacheEntry entry = cacheLocked(application.componentName, info, - info == null ? application.user : info.getUser(), + UserHandleCompat user = info == null ? application.user : info.getUser(); + CacheEntry entry = cacheLocked(application.componentName, info, user, false, useLowResIcon); application.title = entry.title; - application.iconBitmap = entry.icon; + application.iconBitmap = getNonNullIcon(entry, user); application.contentDescription = entry.contentDescription; application.usingLowResIcon = entry.isLowResIcon; } @@ -445,7 +449,7 @@ public class IconCache { ShortcutInfo shortcutInfo, ComponentName component, LauncherActivityInfoCompat info, UserHandleCompat user, boolean usePkgIcon, boolean useLowResIcon) { CacheEntry entry = cacheLocked(component, info, user, usePkgIcon, useLowResIcon); - shortcutInfo.setIcon(entry.icon); + shortcutInfo.setIcon(getNonNullIcon(entry, user)); shortcutInfo.title = entry.title; shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user); shortcutInfo.usingLowResIcon = entry.isLowResIcon; @@ -458,7 +462,7 @@ public class IconCache { String packageName, UserHandleCompat user, boolean useLowResIcon, PackageItemInfo infoOut) { CacheEntry entry = getEntryForPackageLocked(packageName, user, useLowResIcon); - infoOut.iconBitmap = entry.icon; + infoOut.iconBitmap = getNonNullIcon(entry, user); infoOut.title = entry.title; infoOut.usingLowResIcon = entry.isLowResIcon; infoOut.contentDescription = entry.contentDescription; diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java index e48640c93..f1ff48da3 100644 --- a/src/com/android/launcher3/InfoDropTarget.java +++ b/src/com/android/launcher3/InfoDropTarget.java @@ -21,7 +21,6 @@ import android.content.Context; import android.provider.Settings; import android.util.AttributeSet; -import com.android.launcher3.R; import com.android.launcher3.compat.UserHandleCompat; public class InfoDropTarget extends ButtonDropTarget { @@ -66,9 +65,13 @@ public class InfoDropTarget extends ButtonDropTarget { @Override protected boolean supportsDrop(DragSource source, Object info) { - return source.supportsAppInfoDropTarget() && - Settings.Global.getInt(getContext().getContentResolver(), - Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1; + return source.supportsAppInfoDropTarget() && supportsDrop(getContext(), info); + } + + public static boolean supportsDrop(Context context, Object info) { + return (Settings.Global.getInt(context.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1) && + (info instanceof AppInfo || info instanceof PendingAddItemInfo); } @Override diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index 0c69154aa..27dda6404 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -199,12 +199,8 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } } - final boolean exists = LauncherModel.shortcutExists(context, pendingInfo.label, - intent, pendingInfo.user); - if (!exists) { - // Generate a shortcut info to add into the model - addShortcuts.add(pendingInfo.getShortcutInfo()); - } + // Generate a shortcut info to add into the model + addShortcuts.add(pendingInfo.getShortcutInfo()); } // Add the new apps to the model and bind them diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 7364a9f20..8c920f0b8 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -98,12 +98,12 @@ import com.android.launcher3.PagedView.PageSwitchListener; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherActivityInfoCompat; import com.android.launcher3.compat.LauncherAppsCompat; -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.util.LongArrayMap; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.PendingAddWidgetInfo; +import com.android.launcher3.widget.WidgetHostViewLoader; import com.android.launcher3.widget.WidgetsContainerView; import java.io.DataInputStream; @@ -304,7 +304,7 @@ public class Launcher extends Activity @Thunk static LocaleConfiguration sLocaleConfiguration = null; - private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>(); + private static LongArrayMap<FolderInfo> sFolders = new LongArrayMap<>(); private View.OnTouchListener mHapticFeedbackTouchListener; @@ -1060,9 +1060,6 @@ public class Launcher extends Activity getWorkspace().reinflateWidgetsIfNecessary(); reinflateQSBIfNecessary(); - // Process any items that were added while Launcher was away. - InstallShortcutReceiver.disableAndFlushInstallQueue(this); - if (DEBUG_RESUME_TIME) { Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime)); } @@ -1078,7 +1075,10 @@ public class Launcher extends Activity mWorkspace.updateInteractionForState(); mWorkspace.onResume(); - PackageInstallerCompat.getInstance(this).onResume(); + if (!isWorkspaceLoading()) { + // Process any items that were added while Launcher was away. + InstallShortcutReceiver.disableAndFlushInstallQueue(this); + } if (mLauncherCallbacks != null) { mLauncherCallbacks.onResume(); @@ -1089,7 +1089,6 @@ public class Launcher extends Activity protected void onPause() { // Ensure that items added to Launcher are queued until Launcher returns InstallShortcutReceiver.enableInstallQueue(); - PackageInstallerCompat.getInstance(this).onPause(); super.onPause(); mPaused = true; @@ -2050,12 +2049,6 @@ public class Launcher extends Activity TextKeyListener.getInstance().release(); - // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace - // to prevent leaking Launcher activities on orientation change. - if (mModel != null) { - mModel.unbindItemInfosAndClearQueuedBindRunnables(); - } - getContentResolver().unregisterContentObserver(mWidgetObserver); unregisterReceiver(mCloseSystemDialogsReceiver); @@ -3895,7 +3888,7 @@ public class Launcher extends Activity /** * Implementation of the method from LauncherModel.Callbacks. */ - public void bindFolders(final HashMap<Long, FolderInfo> folders) { + public void bindFolders(final LongArrayMap<FolderInfo> folders) { Runnable r = new Runnable() { public void run() { bindFolders(folders); @@ -3904,8 +3897,7 @@ public class Launcher extends Activity if (waitUntilResume(r)) { return; } - sFolders.clear(); - sFolders.putAll(folders); + sFolders = folders.clone(); } /** @@ -3953,7 +3945,7 @@ public class Launcher extends Activity pendingInfo.minSpanX = item.minSpanX; pendingInfo.minSpanY = item.minSpanY; Bundle options = null; - // AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo); + WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo); int newWidgetId = mAppWidgetHost.allocateAppWidgetId(); boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( @@ -4084,7 +4076,7 @@ public class Launcher extends Activity sPendingAddItem = null; } - PackageInstallerCompat.getInstance(this).onFinishBind(); + InstallShortcutReceiver.disableAndFlushInstallQueue(this); if (mLauncherCallbacks != null) { mLauncherCallbacks.finishBindingItems(false); @@ -4236,22 +4228,17 @@ public class Launcher extends Activity * Implementation of the method from LauncherModel.Callbacks. */ @Override - public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) { - if (mWorkspace != null) { - mWorkspace.updatePackageState(installInfo); + public void bindRestoreItemsChange(final HashSet<ItemInfo> updates) { + Runnable r = new Runnable() { + public void run() { + bindRestoreItemsChange(updates); + } + }; + if (waitUntilResume(r)) { + return; } - } - /** - * Update the label and icon of all the icons in a package - * - * Implementation of the method from LauncherModel.Callbacks. - */ - @Override - public void updatePackageBadge(String packageName) { - if (mWorkspace != null) { - mWorkspace.updatePackageBadge(packageName, UserHandleCompat.myUserHandle()); - } + mWorkspace.updateRestoreItems(updates); } /** diff --git a/src/com/android/launcher3/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/LauncherAccessibilityDelegate.java index 8ba02ea5f..a60e16024 100644 --- a/src/com/android/launcher3/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/LauncherAccessibilityDelegate.java @@ -1,16 +1,13 @@ package com.android.launcher3; import android.annotation.TargetApi; -import android.content.res.Resources; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; -import android.support.v4.view.accessibility.AccessibilityEventCompat; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.text.TextUtils; import android.util.SparseArray; import android.view.View; import android.view.View.AccessibilityDelegate; -import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; @@ -68,18 +65,21 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { if (!(host.getTag() instanceof ItemInfo)) return; ItemInfo item = (ItemInfo) host.getTag(); + if (DeleteDropTarget.supportsDrop(item)) { + info.addAction(mActions.get(REMOVE)); + } + if (UninstallDropTarget.supportsDrop(host.getContext(), item)) { + info.addAction(mActions.get(UNINSTALL)); + } + if (InfoDropTarget.supportsDrop(host.getContext(), item)) { + info.addAction(mActions.get(INFO)); + } + if ((item instanceof ShortcutInfo) || (item instanceof LauncherAppWidgetInfo) || (item instanceof FolderInfo)) { - // Workspace shortcut / widget - info.addAction(mActions.get(REMOVE)); info.addAction(mActions.get(MOVE)); - } else if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) { - // App or Widget from customization tray - if (item instanceof AppInfo) { - info.addAction(mActions.get(UNINSTALL)); - } - info.addAction(mActions.get(INFO)); + } if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) { info.addAction(mActions.get(ADD_TO_WORKSPACE)); } } @@ -94,10 +94,9 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { } public boolean performAction(View host, ItemInfo item, int action) { - Resources res = mLauncher.getResources(); if (action == REMOVE) { if (DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host)) { - announceConfirmation(R.string.item_removed_from_workspace); + announceConfirmation(R.string.item_removed); return true; } return false; @@ -105,9 +104,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { InfoDropTarget.startDetailsActivityForInfo(item, mLauncher); return true; } else if (action == UNINSTALL) { - AppInfo info = (AppInfo) item; - mLauncher.startApplicationUninstallActivity(info.componentName, info.flags, info.user); - return true; + return UninstallDropTarget.startUninstallActivity(mLauncher, item); } else if (action == MOVE) { beginAccessibleDrag(host, item); } else if (action == ADD_TO_WORKSPACE) { @@ -158,19 +155,31 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { return mDragInfo; } - public void handleAccessibleDrop(CellLayout targetContainer, Rect dropLocation, + /** + * @param clickedTarget the actual view that was clicked + * @param dropLocation relative to {@param clickedTarget}. If provided, its center is used + * as the actual drop location otherwise the views center is used. + */ + public void handleAccessibleDrop(View clickedTarget, Rect dropLocation, String confirmation) { if (!isInAccessibleDrag()) return; int[] loc = new int[2]; - loc[0] = dropLocation.centerX(); - loc[1] = dropLocation.centerY(); + if (dropLocation == null) { + loc[0] = clickedTarget.getWidth() / 2; + loc[1] = clickedTarget.getHeight() / 2; + } else { + loc[0] = dropLocation.centerX(); + loc[1] = dropLocation.centerY(); + } - mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(targetContainer, loc); + mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(clickedTarget, loc); mLauncher.getDragController().completeAccessibleDrag(loc); endAccessibleDrag(); - announceConfirmation(confirmation); + if (!TextUtils.isEmpty(confirmation)) { + announceConfirmation(confirmation); + } } public void beginAccessibleDrag(View item, ItemInfo info) { diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 6e77d0628..7f31e4999 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -256,15 +256,4 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks { public static boolean isDogfoodBuild() { return getInstance().mBuildInfo.isDogfoodBuild(); } - - public void setPackageState(ArrayList<PackageInstallInfo> installInfo) { - mModel.setPackageState(installInfo); - } - - /** - * Updates the icons and label of all icons for the provided package name. - */ - public void updatePackageBadge(String packageName) { - mModel.updatePackageBadge(packageName); - } } diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java index 9dd8dc50c..03ec4bf7a 100644 --- a/src/com/android/launcher3/LauncherFiles.java +++ b/src/com/android/launcher3/LauncherFiles.java @@ -42,5 +42,6 @@ public class LauncherFiles { // TODO: Delete these files on upgrade public static final List<String> OBSOLETE_FILES = Collections.unmodifiableList(Arrays.asList( "launches.log", - "stats.log")); + "stats.log", + "com.android.launcher3.compat.PackageInstallerCompatV16.queue")); } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index e5561e219..d5dce51df 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -28,7 +28,6 @@ import android.content.Context; import android.content.Intent; import android.content.Intent.ShortcutIconResource; import android.content.IntentFilter; -import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; @@ -42,6 +41,7 @@ import android.os.Build; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; +import android.os.Looper; import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; @@ -60,6 +60,7 @@ import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.util.Thunk; @@ -108,11 +109,6 @@ public class LauncherModel extends BroadcastReceiver @Thunk LoaderTask mLoaderTask; @Thunk boolean mIsLoaderTaskRunning; - // Specific runnable types that are run on the main thread deferred handler, this allows us to - // clear all queued binding runnables when the Launcher activity is destroyed. - private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0; - private static final int MAIN_THREAD_BINDING_RUNNABLE = 1; - private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings"; @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); @@ -146,7 +142,7 @@ public class LauncherModel extends BroadcastReceiver // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by // LauncherModel to their ids - static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>(); + static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>(); // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts // created by LauncherModel that are directly on the home screen (however, no widgets or @@ -158,7 +154,7 @@ public class LauncherModel extends BroadcastReceiver new ArrayList<LauncherAppWidgetInfo>(); // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders() - static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>(); + static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>(); // sBgWorkspaceScreens is the ordered set of workspace screens. static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>(); @@ -187,7 +183,7 @@ public class LauncherModel extends BroadcastReceiver boolean forceAnimateIcons); public void bindScreens(ArrayList<Long> orderedScreenIds); public void bindAddScreens(ArrayList<Long> orderedScreenIds); - public void bindFolders(HashMap<Long,FolderInfo> folders); + public void bindFolders(LongArrayMap<FolderInfo> folders); public void finishBindingItems(); public void bindAppWidget(LauncherAppWidgetInfo info); public void bindAllApplications(ArrayList<AppInfo> apps); @@ -199,8 +195,7 @@ public class LauncherModel extends BroadcastReceiver public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, ArrayList<ShortcutInfo> removed, UserHandleCompat user); public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); - public void updatePackageState(ArrayList<PackageInstallInfo> installInfo); - public void updatePackageBadge(String packageName); + public void bindRestoreItemsChange(HashSet<ItemInfo> updates); public void bindComponentsRemoved(ArrayList<String> packageNames, ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason); public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts); @@ -256,9 +251,6 @@ public class LauncherModel extends BroadcastReceiver /** Runs the specified runnable immediately if called from the main thread, otherwise it is * posted on the main thread handler. */ @Thunk void runOnMainThread(Runnable r) { - runOnMainThread(r, 0); - } - @Thunk void runOnMainThread(Runnable r, int type) { if (sWorkerThread.getThreadId() == Process.myTid()) { // If we are on the worker thread, post onto the main handler mHandler.post(r); @@ -282,30 +274,110 @@ public class LauncherModel extends BroadcastReceiver return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ; } - public void setPackageState(final ArrayList<PackageInstallInfo> installInfo) { - // Process the updated package state - Runnable r = new Runnable() { + public void setPackageState(final PackageInstallInfo installInfo) { + Runnable updateRunnable = new Runnable() { + + @Override public void run() { - Callbacks callbacks = getCallback(); - if (callbacks != null) { - callbacks.updatePackageState(installInfo); + synchronized (sBgLock) { + final HashSet<ItemInfo> updates = new HashSet<>(); + + if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) { + // Ignore install success events as they are handled by Package add events. + return; + } + + for (ItemInfo info : sBgItemsIdMap) { + if (info instanceof ShortcutInfo) { + ShortcutInfo si = (ShortcutInfo) info; + ComponentName cn = si.getTargetComponent(); + if (si.isPromise() && (cn != null) + && installInfo.packageName.equals(cn.getPackageName())) { + si.setInstallProgress(installInfo.progress); + + if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) { + // Mark this info as broken. + si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; + } + updates.add(si); + } + } + } + + for (LauncherAppWidgetInfo widget : sBgAppWidgets) { + if (widget.providerName.getPackageName().equals(installInfo.packageName)) { + widget.installProgress = installInfo.progress; + updates.add(widget); + } + } + + if (!updates.isEmpty()) { + // Push changes to the callback. + Runnable r = new Runnable() { + public void run() { + Callbacks callbacks = getCallback(); + if (callbacks != null) { + callbacks.bindRestoreItemsChange(updates); + } + } + }; + mHandler.post(r); + } } } }; - mHandler.post(r); + runOnWorkerThread(updateRunnable); } - public void updatePackageBadge(final String packageName) { - // Process the updated package badge - Runnable r = new Runnable() { + /** + * Updates the icons and label of all pending icons for the provided package name. + */ + public void updateSessionDisplayInfo(final String packageName) { + Runnable updateRunnable = new Runnable() { + + @Override public void run() { - Callbacks callbacks = getCallback(); - if (callbacks != null) { - callbacks.updatePackageBadge(packageName); + synchronized (sBgLock) { + final ArrayList<ShortcutInfo> updates = new ArrayList<>(); + final UserHandleCompat user = UserHandleCompat.myUserHandle(); + + for (ItemInfo info : sBgItemsIdMap) { + if (info instanceof ShortcutInfo) { + ShortcutInfo si = (ShortcutInfo) info; + ComponentName cn = si.getTargetComponent(); + if (si.isPromise() && (cn != null) + && packageName.equals(cn.getPackageName())) { + if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) { + // For auto install apps update the icon as well as label. + mIconCache.getTitleAndIcon(si, + si.promisedIntent, user, + si.shouldUseLowResIcon()); + } else { + // Only update the icon for restored apps. + si.updateIcon(mIconCache); + } + updates.add(si); + } + } + } + + if (!updates.isEmpty()) { + // Push changes to the callback. + Runnable r = new Runnable() { + public void run() { + Callbacks callbacks = getCallback(); + if (callbacks != null) { + callbacks.bindShortcutsChanged(updates, + new ArrayList<ShortcutInfo>(), user); + } + } + }; + mHandler.post(r); + } } } }; - mHandler.post(r); + runOnWorkerThread(updateRunnable); } public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) { @@ -537,8 +609,7 @@ public class LauncherModel extends BroadcastReceiver for (ItemInfo item : workspaceApps) { if (!allowDuplicate && item instanceof ShortcutInfo) { // Short-circuit this logic if the icon exists somewhere on the workspace - if (shortcutExists(context, item.title.toString(), - item.getIntent(), item.user)) { + if (shortcutExists(context, item.getIntent(), item.user)) { continue; } } @@ -600,7 +671,7 @@ public class LauncherModel extends BroadcastReceiver runOnWorkerThread(r); } - public void unbindItemInfosAndClearQueuedBindRunnables() { + private void unbindItemInfosAndClearQueuedBindRunnables() { if (sWorkerThread.getThreadId() == Process.myTid()) { throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " + "main thread"); @@ -610,8 +681,9 @@ public class LauncherModel extends BroadcastReceiver synchronized (mDeferredBindRunnables) { mDeferredBindRunnables.clear(); } - // Remove any queued bind runnables - mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE); + + // Remove any queued UI runnables + mHandler.cancelAll(); // Unbind all the workspace items unbindWorkspaceItemsOnMainThread(); } @@ -620,19 +692,15 @@ public class LauncherModel extends BroadcastReceiver void unbindWorkspaceItemsOnMainThread() { // Ensure that we don't use the same workspace items data structure on the main thread // by making a copy of workspace items first. - final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>(); - final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>(); + final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>(); synchronized (sBgLock) { - tmpWorkspaceItems.addAll(sBgWorkspaceItems); - tmpAppWidgets.addAll(sBgAppWidgets); + tmpItems.addAll(sBgWorkspaceItems); + tmpItems.addAll(sBgAppWidgets); } Runnable r = new Runnable() { @Override public void run() { - for (ItemInfo item : tmpWorkspaceItems) { - item.unbind(); - } - for (ItemInfo item : tmpAppWidgets) { + for (ItemInfo item : tmpItems) { item.unbind(); } } @@ -904,47 +972,48 @@ public class LauncherModel extends BroadcastReceiver } /** - * Returns true if the shortcuts already exists in the database. - * we identify a shortcut by its title and intent. + * Returns true if the shortcuts already exists on the workspace. This must be called after + * the workspace has been loaded. We identify a shortcut by its intent. + * TODO: Throw exception is above condition is not met. */ - static boolean shortcutExists(Context context, String title, Intent intent, - UserHandleCompat user) { - final ContentResolver cr = context.getContentResolver(); + @Thunk static boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) { final Intent intentWithPkg, intentWithoutPkg; - + final String packageName; if (intent.getComponent() != null) { // If component is not null, an intent with null package will produce // the same result and should also be a match. + packageName = intent.getComponent().getPackageName(); if (intent.getPackage() != null) { intentWithPkg = intent; intentWithoutPkg = new Intent(intent).setPackage(null); } else { - intentWithPkg = new Intent(intent).setPackage( - intent.getComponent().getPackageName()); + intentWithPkg = new Intent(intent).setPackage(packageName); intentWithoutPkg = intent; } } else { intentWithPkg = intent; intentWithoutPkg = intent; + packageName = intent.getPackage(); } - String userSerial = Long.toString(UserManagerCompat.getInstance(context) - .getSerialNumberForUser(user)); - Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, - new String[] { "title", "intent", "profileId" }, - "title=? and (intent=? or intent=?) and profileId=?", - new String[] { title, intentWithPkg.toUri(0), intentWithoutPkg.toUri(0), userSerial }, - null); - try { - return c.moveToFirst(); - } finally { - c.close(); + + synchronized (sBgLock) { + for (ItemInfo item : sBgItemsIdMap) { + if (item instanceof ShortcutInfo) { + ShortcutInfo info = (ShortcutInfo) item; + if (intentWithPkg.equals(info.getIntent()) + || intentWithoutPkg.equals(info.getIntent())) { + return true; + } + } + } } + return false; } /** * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList. */ - FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) { + FolderInfo getFolderById(Context context, LongArrayMap<FolderInfo> folderList, long id) { final ContentResolver cr = context.getContentResolver(); Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null, "_id=? and (itemType=? or itemType=?)", @@ -1064,7 +1133,7 @@ public class LauncherModel extends BroadcastReceiver return cn.getPackageName().equals(pn) && info.user.equals(user); } }; - return filterItemInfos(sBgItemsIdMap.values(), filter); + return filterItemInfos(sBgItemsIdMap, filter); } /** @@ -1104,7 +1173,7 @@ public class LauncherModel extends BroadcastReceiver switch (item.itemType) { case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: sBgFolders.remove(item.id); - for (ItemInfo info: sBgItemsIdMap.values()) { + for (ItemInfo info: sBgItemsIdMap) { if (info.container == item.id) { // We are deleting a folder which still contains items that // think they are contained by that folder. @@ -1217,6 +1286,9 @@ public class LauncherModel extends BroadcastReceiver */ public void initialize(Callbacks callbacks) { synchronized (mLock) { + // Disconnect any of the callbacks and drawables associated with ItemInfos on the + // workspace to prevent leaking Launcher activities on orientation change. + unbindItemInfosAndClearQueuedBindRunnables(); mCallbacks = new WeakReference<Callbacks>(callbacks); } } @@ -1366,6 +1438,8 @@ public class LauncherModel extends BroadcastReceiver } public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) { + // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems + InstallShortcutReceiver.enableInstallQueue(); synchronized (mLock) { if (DEBUG_LOADERS) { Log.d(TAG, "startLoader isLaunching=" + isLaunching); @@ -1404,7 +1478,7 @@ public class LauncherModel extends BroadcastReceiver mDeferredBindRunnables.clear(); } for (final Runnable r : deferredBindRunnables) { - mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE); + mHandler.post(r); } } } @@ -1676,7 +1750,7 @@ public class LauncherModel extends BroadcastReceiver } // check & update map of what's occupied; used to discard overlapping/invalid items - private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item) { + private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item) { LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); final int countX = (int) grid.numColumns; @@ -1812,7 +1886,7 @@ public class LauncherModel extends BroadcastReceiver synchronized (sBgLock) { clearSBgDataStructures(); - final HashSet<String> installingPkgs = PackageInstallerCompat + final HashMap<String, Integer> installingPkgs = PackageInstallerCompat .getInstance(mContext).updateAndGetActiveSessionCache(); final ArrayList<Long> itemsToRemove = new ArrayList<Long>(); @@ -1824,7 +1898,7 @@ public class LauncherModel extends BroadcastReceiver // +1 for the hotseat (it can be larger than the workspace) // Load workspace in reverse order to ensure that latest items are loaded first (and // before any earlier duplicates) - final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>(); + final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>(); try { final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); @@ -1951,7 +2025,7 @@ public class LauncherModel extends BroadcastReceiver if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) { // Restore has started once. - } else if (installingPkgs.contains(cn.getPackageName())) { + } else if (installingPkgs.containsKey(cn.getPackageName())) { // App restore has started. Update the flag promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED; ContentValues values = new ContentValues(); @@ -2093,6 +2167,18 @@ public class LauncherModel extends BroadcastReceiver break; } + if (restored) { + ComponentName cn = info.getTargetComponent(); + if (cn != null) { + Integer progress = installingPkgs.get(cn.getPackageName()); + if (progress != null) { + info.setInstallProgress(progress); + } else { + info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; + } + } + } + switch (container) { case LauncherSettings.Favorites.CONTAINER_DESKTOP: case LauncherSettings.Favorites.CONTAINER_HOTSEAT: @@ -2220,10 +2306,11 @@ public class LauncherModel extends BroadcastReceiver appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, component); appWidgetInfo.restoreStatus = restoreStatus; + Integer installProgress = installingPkgs.get(component.getPackageName()); if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) != 0) { // Restore has started once. - } else if (installingPkgs.contains(component.getPackageName())) { + } else if (installProgress != null) { // App restore has started. Update the flag appWidgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; @@ -2233,6 +2320,9 @@ public class LauncherModel extends BroadcastReceiver itemsToRemove.add(id); continue; } + + appWidgetInfo.installProgress = + installProgress == null ? 0 : installProgress; } appWidgetInfo.id = id; @@ -2347,7 +2437,7 @@ public class LauncherModel extends BroadcastReceiver // Remove any empty screens ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens); - for (ItemInfo item: sBgItemsIdMap.values()) { + for (ItemInfo item: sBgItemsIdMap) { long screenId = item.screenId; if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && unusedScreens.contains(screenId)) { @@ -2372,14 +2462,13 @@ public class LauncherModel extends BroadcastReceiver for (int y = 0; y < countY; y++) { String line = ""; - Iterator<Long> iter = occupied.keySet().iterator(); - while (iter.hasNext()) { - long screenId = iter.next(); + for (int i = 0; i < nScreens; i++) { + long screenId = occupied.keyAt(i); if (screenId > 0) { line += " | "; } + ItemInfo[][] screen = occupied.valueAt(i); for (int x = 0; x < countX; x++) { - ItemInfo[][] screen = occupied.get(screenId); if (x < screen.length && y < screen[x].length) { line += (screen[x][y] != null) ? "#" : "."; } else { @@ -2470,14 +2559,17 @@ public class LauncherModel extends BroadcastReceiver /** Filters the set of folders which are on the specified screen. */ private void filterCurrentFolders(long currentScreenId, - HashMap<Long, ItemInfo> itemsIdMap, - HashMap<Long, FolderInfo> folders, - HashMap<Long, FolderInfo> currentScreenFolders, - HashMap<Long, FolderInfo> otherScreenFolders) { + LongArrayMap<ItemInfo> itemsIdMap, + LongArrayMap<FolderInfo> folders, + LongArrayMap<FolderInfo> currentScreenFolders, + LongArrayMap<FolderInfo> otherScreenFolders) { + + int total = folders.size(); + for (int i = 0; i < total; i++) { + long id = folders.keyAt(i); + FolderInfo folder = folders.valueAt(i); - for (long id : folders.keySet()) { ItemInfo info = itemsIdMap.get(id); - FolderInfo folder = folders.get(id); if (info == null || folder == null) continue; if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && info.screenId == currentScreenId) { @@ -2521,13 +2613,13 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); + runOnMainThread(r); } private void bindWorkspaceItems(final Callbacks oldCallbacks, final ArrayList<ItemInfo> workspaceItems, final ArrayList<LauncherAppWidgetInfo> appWidgets, - final HashMap<Long, FolderInfo> folders, + final LongArrayMap<FolderInfo> folders, ArrayList<Runnable> deferredBindRunnables) { final boolean postOnMainThread = (deferredBindRunnables != null); @@ -2552,7 +2644,7 @@ public class LauncherModel extends BroadcastReceiver deferredBindRunnables.add(r); } } else { - runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); + runOnMainThread(r); } } @@ -2571,7 +2663,7 @@ public class LauncherModel extends BroadcastReceiver deferredBindRunnables.add(r); } } else { - runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); + runOnMainThread(r); } } @@ -2590,7 +2682,7 @@ public class LauncherModel extends BroadcastReceiver if (postOnMainThread) { deferredBindRunnables.add(r); } else { - runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); + runOnMainThread(r); } } } @@ -2615,15 +2707,18 @@ public class LauncherModel extends BroadcastReceiver ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>(); ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<LauncherAppWidgetInfo>(); - HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>(); - HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>(); ArrayList<Long> orderedScreenIds = new ArrayList<Long>(); + + final LongArrayMap<FolderInfo> folders; + final LongArrayMap<ItemInfo> itemsIdMap; + synchronized (sBgLock) { workspaceItems.addAll(sBgWorkspaceItems); appWidgets.addAll(sBgAppWidgets); - folders.putAll(sBgFolders); - itemsIdMap.putAll(sBgItemsIdMap); orderedScreenIds.addAll(sBgWorkspaceScreens); + + folders = sBgFolders.clone(); + itemsIdMap = sBgItemsIdMap.clone(); } final boolean isLoadingSynchronously = @@ -2649,8 +2744,8 @@ public class LauncherModel extends BroadcastReceiver new ArrayList<LauncherAppWidgetInfo>(); ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<LauncherAppWidgetInfo>(); - HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>(); - HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>(); + LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>(); + LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>(); filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, otherWorkspaceItems); @@ -2670,7 +2765,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); + runOnMainThread(r); bindWorkspaceScreens(oldCallbacks, orderedScreenIds); @@ -2686,7 +2781,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); + runOnMainThread(r); } // Load all the remaining pages (if we are loading synchronously, we want to defer this @@ -2719,7 +2814,7 @@ public class LauncherModel extends BroadcastReceiver mDeferredBindRunnables.add(r); } } else { - runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); + runOnMainThread(r); } } @@ -2790,8 +2885,6 @@ public class LauncherModel extends BroadcastReceiver // Clear the list of apps mBgAllAppsList.clear(); - SharedPreferences prefs = mContext.getSharedPreferences( - LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE); for (UserHandleCompat user : profiles) { // Query for the set of apps final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; @@ -2815,7 +2908,7 @@ public class LauncherModel extends BroadcastReceiver if (!updatedPackages.isEmpty()) { final ArrayList<ShortcutInfo> updates = new ArrayList<ShortcutInfo>(); synchronized (sBgLock) { - for (ItemInfo info : sBgItemsIdMap.values()) { + for (ItemInfo info : sBgItemsIdMap) { if (info instanceof ShortcutInfo && user.equals(info.user) && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { ShortcutInfo si = (ShortcutInfo) info; @@ -3064,7 +3157,7 @@ public class LauncherModel extends BroadcastReceiver HashSet<String> packageSet = new HashSet<String>(Arrays.asList(packages)); synchronized (sBgLock) { - for (ItemInfo info : sBgItemsIdMap.values()) { + for (ItemInfo info : sBgItemsIdMap) { if (info instanceof ShortcutInfo && mUser.equals(info.user)) { ShortcutInfo si = (ShortcutInfo) info; boolean infoUpdated = false; @@ -3112,15 +3205,13 @@ public class LauncherModel extends BroadcastReceiver } // Restore the shortcut. - si.intent = si.promisedIntent; - si.promisedIntent = null; - si.status &= ~ShortcutInfo.FLAG_RESTORED_ICON - & ~ShortcutInfo.FLAG_AUTOINTALL_ICON - & ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; if (appInfo != null) { si.flags = appInfo.flags; } + si.intent = si.promisedIntent; + si.promisedIntent = null; + si.status = ShortcutInfo.DEFAULT; infoUpdated = true; si.updateIcon(mIconCache); } @@ -3353,12 +3444,10 @@ public class LauncherModel extends BroadcastReceiver if (!TextUtils.isEmpty(title)) { info.title = title; } - info.status = ShortcutInfo.FLAG_RESTORED_ICON; } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) { if (TextUtils.isEmpty(info.title)) { info.title = (cursor != null) ? cursor.getString(titleIndex) : ""; } - info.status = ShortcutInfo.FLAG_AUTOINTALL_ICON; } else { throw new InvalidParameterException("Invalid restoreType " + promiseType); } @@ -3367,6 +3456,7 @@ public class LauncherModel extends BroadcastReceiver info.title.toString(), info.user); info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; info.promisedIntent = intent; + info.status = promiseType; return info; } @@ -3443,7 +3533,7 @@ public class LauncherModel extends BroadcastReceiver return info; } - static ArrayList<ItemInfo> filterItemInfos(Collection<ItemInfo> infos, + static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos, ItemInfoFilter f) { HashSet<ItemInfo> filtered = new HashSet<ItemInfo>(); for (ItemInfo i : infos) { @@ -3484,7 +3574,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - return filterItemInfos(sBgItemsIdMap.values(), filter); + return filterItemInfos(sBgItemsIdMap, filter); } /** @@ -3594,7 +3684,7 @@ public class LauncherModel extends BroadcastReceiver * Return an existing FolderInfo object if we have encountered this ID previously, * or make a new one. */ - @Thunk static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) { + @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) { // See if a placeholder was created for us already FolderInfo folderInfo = folders.get(id); if (folderInfo == null) { @@ -3669,4 +3759,11 @@ public class LauncherModel extends BroadcastReceiver return sBgFolders.get(folderId); } } + + /** + * @return the looper for the worker thread which can be used to start background tasks. + */ + public static Looper getWorkerLooper() { + return sWorkerThread.getLooper(); + } } diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java index af8bc7580..44a76b73b 100644 --- a/src/com/android/launcher3/SearchDropTargetBar.java +++ b/src/com/android/launcher3/SearchDropTargetBar.java @@ -180,6 +180,9 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D prepareStartAnimation(mDropTargetBar); mShowDropTargetBarAnim.start(); hideSearchBar(true); + if (mQSBSearchBar != null) { + mQSBSearchBar.setVisibility(View.GONE); + } } /** @@ -190,6 +193,9 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D prepareStartAnimation(mDropTargetBar); mShowDropTargetBarAnim.reverse(); showSearchBar(true); + if (mQSBSearchBar != null) { + mQSBSearchBar.setVisibility(View.VISIBLE); + } } /* @@ -228,4 +234,13 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D return null; } } + + public void enableAccessibleDrag(boolean enable) { + if (mQSBSearchBar != null) { + mQSBSearchBar.setVisibility(enable ? View.GONE : View.VISIBLE); + } + mInfoDropTarget.enableAccessibleDrag(enable); + mDeleteDropTarget.enableAccessibleDrag(enable); + mUninstallDropTarget.enableAccessibleDrag(enable); + } } diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index 5bef845bb..6354fcd28 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -214,6 +214,7 @@ public class ShortcutInfo extends ItemInfo { String uri = promisedIntent != null ? promisedIntent.toUri(0) : (intent != null ? intent.toUri(0) : null); values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri); + values.put(LauncherSettings.Favorites.RESTORED, status); if (customIcon) { values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE, diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java index 4a7fffeb2..4c52d7e0f 100644 --- a/src/com/android/launcher3/UninstallDropTarget.java +++ b/src/com/android/launcher3/UninstallDropTarget.java @@ -33,9 +33,12 @@ public class UninstallDropTarget extends ButtonDropTarget { @Override protected boolean supportsDrop(DragSource source, Object info) { + return supportsDrop(getContext(), info); + } + + public static boolean supportsDrop(Context context, Object info) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - UserManager userManager = (UserManager) - getContext().getSystemService(Context.USER_SERVICE); + UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); Bundle restrictions = userManager.getUserRestrictions(); if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false) || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false)) { @@ -78,8 +81,7 @@ public class UninstallDropTarget extends ButtonDropTarget { void completeDrop(final DragObject d) { final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(d.dragInfo); final UserHandleCompat user = ((ItemInfo) d.dragInfo).user; - if (mLauncher.startApplicationUninstallActivity( - componentInfo.first, componentInfo.second, user)) { + if (startUninstallActivity(mLauncher, d.dragInfo)) { final Runnable checkIfUninstallWasSuccess = new Runnable() { @Override @@ -96,6 +98,13 @@ public class UninstallDropTarget extends ButtonDropTarget { } } + public static boolean startUninstallActivity(Launcher launcher, Object info) { + final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info); + final UserHandleCompat user = ((ItemInfo) info).user; + return launcher.startApplicationUninstallActivity( + componentInfo.first, componentInfo.second, user); + } + @Thunk void sendUninstallResult(DragSource target, boolean result) { if (target instanceof UninstallSource) { ((UninstallSource) target).onUninstallActivityReturned(result); diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 9e680fb82..2efd20739 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -62,9 +62,8 @@ import com.android.launcher3.Launcher.CustomContentCallbacks; import com.android.launcher3.Launcher.LauncherOverlay; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.UninstallDropTarget.UninstallSource; -import com.android.launcher3.compat.PackageInstallerCompat; -import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.WallpaperUtils; import com.android.launcher3.widget.PendingAddShortcutInfo; @@ -73,7 +72,6 @@ import com.android.launcher3.widget.PendingAddWidgetInfo; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.concurrent.atomic.AtomicInteger; /** @@ -122,7 +120,7 @@ public class Workspace extends SmoothPagedView final static long EXTRA_EMPTY_SCREEN_ID = -201; private final static long CUSTOM_CONTENT_SCREEN_ID = -301; - @Thunk HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>(); + @Thunk LongArrayMap<CellLayout> mWorkspaceScreens = new LongArrayMap<>(); @Thunk ArrayList<Long> mScreenOrder = new ArrayList<Long>(); @Thunk Runnable mRemoveEmptyScreenRunnable; @@ -836,12 +834,9 @@ public class Workspace extends SmoothPagedView } public long getIdForScreen(CellLayout layout) { - Iterator<Long> iter = mWorkspaceScreens.keySet().iterator(); - while (iter.hasNext()) { - long id = iter.next(); - if (mWorkspaceScreens.get(id) == layout) { - return id; - } + int index = mWorkspaceScreens.indexOfValue(layout); + if (index != -1) { + return mWorkspaceScreens.keyAt(index); } return -1; } @@ -876,8 +871,10 @@ public class Workspace extends SmoothPagedView int currentPage = getNextPage(); ArrayList<Long> removeScreens = new ArrayList<Long>(); - for (Long id: mWorkspaceScreens.keySet()) { - CellLayout cl = mWorkspaceScreens.get(id); + int total = mWorkspaceScreens.size(); + for (int i = 0; i < total; i++) { + long id = mWorkspaceScreens.keyAt(i); + CellLayout cl = mWorkspaceScreens.valueAt(i); if (id >= 0 && cl.getShortcutsAndWidgets().getChildCount() == 0) { removeScreens.add(id); } @@ -1617,6 +1614,7 @@ public class Workspace extends SmoothPagedView // Reset our click listener setOnClickListener(mLauncher); } + mLauncher.getSearchBar().enableAccessibleDrag(enable); mLauncher.getHotseat().getLayout().enableAccessibleDrag(enable); } @@ -4358,32 +4356,17 @@ public class Workspace extends SmoothPagedView removeItemsByPackageName(packages, user); } - public void updatePackageBadge(final String packageName, final UserHandleCompat user) { + public void updateRestoreItems(final HashSet<ItemInfo> updates) { mapOverItems(MAP_RECURSE, new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View v, View parent) { - if (info instanceof ShortcutInfo && v instanceof BubbleTextView) { - ShortcutInfo shortcutInfo = (ShortcutInfo) info; - ComponentName cn = shortcutInfo.getTargetComponent(); - if (user.equals(shortcutInfo.user) && cn != null - && shortcutInfo.isPromise() - && packageName.equals(cn.getPackageName())) { - if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) { - // For auto install apps update the icon as well as label. - mIconCache.getTitleAndIcon(shortcutInfo, - shortcutInfo.promisedIntent, user, - shortcutInfo.shouldUseLowResIcon()); - } else { - // Only update the icon for restored apps. - shortcutInfo.updateIcon(mIconCache); - } - BubbleTextView shortcut = (BubbleTextView) v; - shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, true, false); - - if (parent != null) { - parent.invalidate(); - } - } + if (info instanceof ShortcutInfo && v instanceof BubbleTextView + && updates.contains(info)) { + ((BubbleTextView) v).applyState(false); + } else if (v instanceof PendingAppWidgetHostView + && info instanceof LauncherAppWidgetInfo + && updates.contains(info)) { + ((PendingAppWidgetHostView) v).applyState(); } // process all the shortcuts return false; @@ -4391,42 +4374,6 @@ public class Workspace extends SmoothPagedView }); } - public void updatePackageState(ArrayList<PackageInstallInfo> installInfos) { - for (final PackageInstallInfo installInfo : installInfos) { - if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) { - continue; - } - - mapOverItems(MAP_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v, View parent) { - if (info instanceof ShortcutInfo && v instanceof BubbleTextView) { - ShortcutInfo si = (ShortcutInfo) info; - ComponentName cn = si.getTargetComponent(); - if (si.isPromise() && (cn != null) - && installInfo.packageName.equals(cn.getPackageName())) { - si.setInstallProgress(installInfo.progress); - if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) { - // Mark this info as broken. - si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; - } - ((BubbleTextView)v).applyState(false); - } - } else if (v instanceof PendingAppWidgetHostView - && info instanceof LauncherAppWidgetInfo - && ((LauncherAppWidgetInfo) info).providerName.getPackageName() - .equals(installInfo.packageName)) { - ((LauncherAppWidgetInfo) info).installProgress = installInfo.progress; - ((PendingAppWidgetHostView) v).applyState(); - } - - // process all the shortcuts - return false; - } - }); - } - } - void widgetsRestored(ArrayList<LauncherAppWidgetInfo> changedInfo) { if (!changedInfo.isEmpty()) { DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo, diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java index 0eb8754e8..c49908328 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompat.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java @@ -20,7 +20,7 @@ import android.content.Context; import com.android.launcher3.Utilities; -import java.util.HashSet; +import java.util.HashMap; public abstract class PackageInstallerCompat { @@ -37,25 +37,20 @@ public abstract class PackageInstallerCompat { if (Utilities.isLmpOrAbove()) { sInstance = new PackageInstallerCompatVL(context); } else { - sInstance = new PackageInstallerCompatV16(context) { }; + sInstance = new PackageInstallerCompatV16(); } } return sInstance; } } - public abstract HashSet<String> updateAndGetActiveSessionCache(); - - public abstract void onPause(); - - public abstract void onResume(); - - public abstract void onFinishBind(); + /** + * @return a map of active installs to their progress + */ + public abstract HashMap<String, Integer> updateAndGetActiveSessionCache(); public abstract void onStop(); - public abstract void recordPackageUpdate(String packageName, int state, int progress); - public static final class PackageInstallInfo { public final String packageName; diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatV16.java b/src/com/android/launcher3/compat/PackageInstallerCompatV16.java index 1910d22ae..654e34968 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompatV16.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompatV16.java @@ -16,160 +16,17 @@ package com.android.launcher3.compat; -import android.content.Context; -import android.content.SharedPreferences; -import android.text.TextUtils; -import android.util.Log; - -import com.android.launcher3.LauncherAppState; - -import org.json.JSONException; -import org.json.JSONObject; -import org.json.JSONStringer; -import org.json.JSONTokener; - -import java.util.ArrayList; -import java.util.HashSet; +import java.util.HashMap; public class PackageInstallerCompatV16 extends PackageInstallerCompat { - private static final String TAG = "PackageInstallerCompatV16"; - private static final boolean DEBUG = false; - - private static final String KEY_PROGRESS = "progress"; - private static final String KEY_STATE = "state"; - - private static final String PREFS = - "com.android.launcher3.compat.PackageInstallerCompatV16.queue"; - - protected final SharedPreferences mPrefs; - - boolean mUseQueue; - boolean mFinishedBind; - boolean mReplayPending; - - PackageInstallerCompatV16(Context context) { - mPrefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE); - } - - @Override - public void onPause() { - mUseQueue = true; - if (DEBUG) Log.d(TAG, "updates paused"); - } - - @Override - public void onResume() { - mUseQueue = false; - if (mFinishedBind) { - replayUpdates(); - } - } - - @Override - public void onFinishBind() { - mFinishedBind = true; - if (!mUseQueue) { - replayUpdates(); - } - } + PackageInstallerCompatV16() { } @Override public void onStop() { } - private void replayUpdates() { - if (DEBUG) Log.d(TAG, "updates resumed"); - LauncherAppState app = LauncherAppState.getInstanceNoCreate(); - if (app == null) { - mReplayPending = true; // try again later - if (DEBUG) Log.d(TAG, "app is null, delaying send"); - return; - } - mReplayPending = false; - ArrayList<PackageInstallInfo> updates = new ArrayList<PackageInstallInfo>(); - for (String packageName: mPrefs.getAll().keySet()) { - final String json = mPrefs.getString(packageName, null); - if (!TextUtils.isEmpty(json)) { - updates.add(infoFromJson(packageName, json)); - } - } - if (!updates.isEmpty()) { - sendUpdate(app, updates); - } - } - - /** - * This should be called by the implementations to register a package update. - */ - @Override - public synchronized void recordPackageUpdate(String packageName, int state, int progress) { - SharedPreferences.Editor editor = mPrefs.edit(); - PackageInstallInfo installInfo = new PackageInstallInfo(packageName); - installInfo.progress = progress; - installInfo.state = state; - if (state == STATUS_INSTALLED) { - // no longer necessary to track this package - editor.remove(packageName); - if (DEBUG) Log.d(TAG, "no longer tracking " + packageName); - } else { - editor.putString(packageName, infoToJson(installInfo)); - if (DEBUG) - Log.d(TAG, "saved state: " + infoToJson(installInfo) - + " for package: " + packageName); - - } - editor.commit(); - - if (!mUseQueue) { - if (mReplayPending) { - replayUpdates(); - } else if (state != STATUS_INSTALLED) { - LauncherAppState app = LauncherAppState.getInstanceNoCreate(); - ArrayList<PackageInstallInfo> update = new ArrayList<PackageInstallInfo>(); - update.add(installInfo); - sendUpdate(app, update); - } - } - } - - private void sendUpdate(LauncherAppState app, ArrayList<PackageInstallInfo> updates) { - if (app == null) { - mReplayPending = true; // try again later - if (DEBUG) Log.d(TAG, "app is null, delaying send"); - } else { - app.setPackageState(updates); - } - } - - private static PackageInstallInfo infoFromJson(String packageName, String json) { - PackageInstallInfo info = new PackageInstallInfo(packageName); - try { - JSONObject object = (JSONObject) new JSONTokener(json).nextValue(); - info.state = object.getInt(KEY_STATE); - info.progress = object.getInt(KEY_PROGRESS); - } catch (JSONException e) { - Log.e(TAG, "failed to deserialize app state update", e); - } - return info; - } - - private static String infoToJson(PackageInstallInfo info) { - String value = null; - try { - JSONStringer json = new JSONStringer() - .object() - .key(KEY_STATE).value(info.state) - .key(KEY_PROGRESS).value(info.progress) - .endObject(); - value = json.toString(); - } catch (JSONException e) { - Log.e(TAG, "failed to serialize app state update", e); - } - return value; - } - @Override - public HashSet<String> updateAndGetActiveSessionCache() { - return new HashSet<String>(); + public HashMap<String, Integer> updateAndGetActiveSessionCache() { + return new HashMap<>(); } } diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java index d6d4b8287..395d5f9e2 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java @@ -21,63 +21,41 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionCallback; import android.content.pm.PackageInstaller.SessionInfo; import android.os.Handler; -import android.util.Log; import android.util.SparseArray; import com.android.launcher3.IconCache; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel; import com.android.launcher3.util.Thunk; -import java.util.ArrayList; -import java.util.HashSet; +import java.util.HashMap; -public class PackageInstallerCompatVL extends PackageInstallerCompat implements Runnable { +public class PackageInstallerCompatVL extends PackageInstallerCompat { - private static final String TAG = "PackageInstallerCompatVL"; - private static final boolean DEBUG = false; - - // All updates to these sets must happen on the {@link #mWorker} thread. - @Thunk final SparseArray<SessionInfo> mPendingReplays = new SparseArray<SessionInfo>(); - @Thunk final HashSet<String> mPendingBadgeUpdates = new HashSet<String>(); + @Thunk final SparseArray<String> mActiveSessions = new SparseArray<>(); @Thunk final PackageInstaller mInstaller; private final IconCache mCache; private final Handler mWorker; - private boolean mResumed; - private boolean mBound; - PackageInstallerCompatVL(Context context) { mInstaller = context.getPackageManager().getPackageInstaller(); LauncherAppState.setApplicationContext(context.getApplicationContext()); mCache = LauncherAppState.getInstance().getIconCache(); - mWorker = new Handler(); - - mResumed = false; - mBound = false; + mWorker = new Handler(LauncherModel.getWorkerLooper()); mInstaller.registerSessionCallback(mCallback, mWorker); - - // On start, send updates for all active sessions - mWorker.post(new Runnable() { - - @Override - public void run() { - for (SessionInfo info : mInstaller.getAllSessions()) { - mPendingReplays.append(info.getSessionId(), info); - } - } - }); } @Override - public HashSet<String> updateAndGetActiveSessionCache() { - HashSet<String> activePackages = new HashSet<String>(); + public HashMap<String, Integer> updateAndGetActiveSessionCache() { + HashMap<String, Integer> activePackages = new HashMap<>(); UserHandleCompat user = UserHandleCompat.myUserHandle(); for (SessionInfo info : mInstaller.getAllSessions()) { addSessionInfoToCahce(info, user); if (info.getAppPackageName() != null) { - activePackages.add(info.getAppPackageName()); + activePackages.put(info.getAppPackageName(), (int) (info.getProgress() * 100)); + mActiveSessions.put(info.getSessionId(), info.getAppPackageName()); } } return activePackages; @@ -96,74 +74,10 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat implements mInstaller.unregisterSessionCallback(mCallback); } - @Override - public void onFinishBind() { - mBound = true; - mWorker.post(this); - } - - @Override - public void onPause() { - mResumed = false; - } - - @Override - public void onResume() { - mResumed = true; - mWorker.post(this); - } - - @Override - public void recordPackageUpdate(String packageName, int state, int progress) { - // No op - } - - @Override - public void run() { - // Called on mWorker thread. - replayUpdates(null); - } - - @Thunk void replayUpdates(PackageInstallInfo newInfo) { - if (DEBUG) Log.d(TAG, "updates resumed"); - if (!mResumed || !mBound) { - // Not yet ready - return; - } - if ((mPendingReplays.size() == 0) && (newInfo == null)) { - // Nothing to update - return; - } - + @Thunk void sendUpdate(PackageInstallInfo info) { LauncherAppState app = LauncherAppState.getInstanceNoCreate(); - if (app == null) { - // Try again later - if (DEBUG) Log.d(TAG, "app is null, delaying send"); - return; - } - - ArrayList<PackageInstallInfo> updates = new ArrayList<PackageInstallInfo>(); - if ((newInfo != null) && (newInfo.state != STATUS_INSTALLED)) { - updates.add(newInfo); - } - for (int i = mPendingReplays.size() - 1; i >= 0; i--) { - SessionInfo session = mPendingReplays.valueAt(i); - if (session.getAppPackageName() != null) { - updates.add(new PackageInstallInfo(session.getAppPackageName(), - STATUS_INSTALLING, - (int) (session.getProgress() * 100))); - } - } - mPendingReplays.clear(); - if (!updates.isEmpty()) { - app.setPackageState(updates); - } - - if (!mPendingBadgeUpdates.isEmpty()) { - for (String pkg : mPendingBadgeUpdates) { - app.updatePackageBadge(pkg); - } - mPendingBadgeUpdates.clear(); + if (app != null) { + app.getModel().setPackageState(info); } } @@ -171,19 +85,18 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat implements @Override public void onCreated(int sessionId) { - pushSessionBadgeToLauncher(sessionId); + pushSessionDisplayToLauncher(sessionId); } @Override public void onFinished(int sessionId, boolean success) { - mPendingReplays.remove(sessionId); - SessionInfo session = mInstaller.getSessionInfo(sessionId); - if ((session != null) && (session.getAppPackageName() != null)) { - mPendingBadgeUpdates.remove(session.getAppPackageName()); - // Replay all updates with a one time update for this installed package. No - // need to store this record for future updates, as the app list will get - // refreshed on resume. - replayUpdates(new PackageInstallInfo(session.getAppPackageName(), + // For a finished session, we can't get the session info. So use the + // packageName from our local cache. + String packageName = mActiveSessions.get(sessionId); + mActiveSessions.remove(sessionId); + + if (packageName != null) { + sendUpdate(new PackageInstallInfo(packageName, success ? STATUS_INSTALLED : STATUS_FAILED, 0)); } } @@ -192,8 +105,9 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat implements public void onProgressChanged(int sessionId, float progress) { SessionInfo session = mInstaller.getSessionInfo(sessionId); if (session != null) { - mPendingReplays.put(sessionId, session); - replayUpdates(null); + sendUpdate(new PackageInstallInfo(session.getAppPackageName(), + STATUS_INSTALLING, + (int) (session.getProgress() * 100))); } } @@ -202,18 +116,18 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat implements @Override public void onBadgingChanged(int sessionId) { - pushSessionBadgeToLauncher(sessionId); + pushSessionDisplayToLauncher(sessionId); } - private void pushSessionBadgeToLauncher(int sessionId) { + private void pushSessionDisplayToLauncher(int sessionId) { SessionInfo session = mInstaller.getSessionInfo(sessionId); if (session != null) { addSessionInfoToCahce(session, UserHandleCompat.myUserHandle()); - if (session.getAppPackageName() != null) { - mPendingBadgeUpdates.add(session.getAppPackageName()); + LauncherAppState app = LauncherAppState.getInstanceNoCreate(); + + if (app != null) { + app.getModel().updateSessionDisplayInfo(session.getAppPackageName()); } - mPendingReplays.put(sessionId, session); - replayUpdates(null); } } }; diff --git a/src/com/android/launcher3/util/LongArrayMap.java b/src/com/android/launcher3/util/LongArrayMap.java new file mode 100644 index 000000000..e3c96cd42 --- /dev/null +++ b/src/com/android/launcher3/util/LongArrayMap.java @@ -0,0 +1,65 @@ +/* + * 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.util.LongSparseArray; + +import java.util.Iterator; + +/** + * Extension of {@link LongSparseArray} with some utility methods. + */ +public class LongArrayMap<E> extends LongSparseArray<E> implements Iterable<E> { + + public boolean containsKey(long key) { + return indexOfKey(key) >= 0; + } + + public boolean isEmpty() { + return size() <= 0; + } + + @Override + public LongArrayMap<E> clone() { + return (LongArrayMap<E>) super.clone(); + } + + @Override + public Iterator<E> iterator() { + return new ValueIterator(); + } + + private class ValueIterator implements Iterator<E> { + + private int mNextIndex = 0; + + @Override + public boolean hasNext() { + return mNextIndex < size(); + } + + @Override + public E next() { + return valueAt(mNextIndex ++); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index 1ae75c3cc..0bc7333ec 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -24,8 +24,6 @@ import android.graphics.Bitmap; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; -import android.util.TypedValue; -import android.view.MotionEvent; import android.view.View; import android.view.View.OnLayoutChangeListener; import android.widget.ImageView; @@ -43,7 +41,7 @@ import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest; import com.android.launcher3.compat.AppWidgetManagerCompat; /** - * The linear layout used strictly for the widget tray. + * Represents the individual cell of the widget inside the widget tray. */ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { @@ -53,14 +51,12 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { private static final int FADE_IN_DURATION_MS = 70; private int mPresetPreviewSize; - private static WidgetCell sShortpressTarget = null; - + private ImageView mWidgetImage; + private TextView mWidgetName; + private TextView mWidgetDims; private final Rect mOriginalImagePadding = new Rect(); private String mDimensionsFormatString; - private CheckForShortPress mPendingCheckForShortPress = null; - private ShortPressListener mShortPressListener = null; - private boolean mShortPressTriggered = false; private boolean mIsAppWidget; private Object mInfo; @@ -92,57 +88,27 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { protected void onFinishInflate() { super.onFinishInflate(); - final ImageView image = (ImageView) findViewById(R.id.widget_preview); - mOriginalImagePadding.left = image.getPaddingLeft(); - mOriginalImagePadding.top = image.getPaddingTop(); - mOriginalImagePadding.right = image.getPaddingRight(); - mOriginalImagePadding.bottom = image.getPaddingBottom(); + mWidgetImage = (ImageView) findViewById(R.id.widget_preview); + mOriginalImagePadding.left = mWidgetImage.getPaddingLeft(); + mOriginalImagePadding.top = mWidgetImage.getPaddingTop(); + mOriginalImagePadding.right = mWidgetImage.getPaddingRight(); + mOriginalImagePadding.bottom = mWidgetImage.getPaddingBottom(); // Ensure we are using the right text size - LauncherAppState app = LauncherAppState.getInstance(); - DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); - TextView name = (TextView) findViewById(R.id.widget_name); - if (name != null) { - name.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx); - } - TextView dims = (TextView) findViewById(R.id.widget_dims); - if (dims != null) { - dims.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx); - } - } - - @Override - protected void onDetachedFromWindow() { - if (DEBUG) { - Log.d(TAG, String.format("[tag=%s] onDetachedFromWindow", getTagToString())); - } - super.onDetachedFromWindow(); - deletePreview(false); + DeviceProfile profile = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile(); + mWidgetName = ((TextView) findViewById(R.id.widget_name)); + mWidgetDims = ((TextView) findViewById(R.id.widget_dims)); } public void reset() { - ImageView image = (ImageView) findViewById(R.id.widget_preview); - final TextView name = (TextView) findViewById(R.id.widget_name); - final TextView dims = (TextView) findViewById(R.id.widget_dims); - image.setImageDrawable(null); - name.setText(null); - dims.setText(null); - } - - public void deletePreview(boolean recycleImage) { - if (recycleImage) { - final ImageView image = (ImageView) findViewById(R.id.widget_preview); - if (image != null) { - image.setImageDrawable(null); - } - } - - if (mActiveRequest != null) { - mActiveRequest.cancel(recycleImage); - mActiveRequest = null; - } + mWidgetImage.setImageDrawable(null); + mWidgetName.setText(null); + mWidgetDims.setText(null); } + /** + * Apply the widget provider info to the view. + */ public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info, int maxWidth, WidgetPreviewLoader loader) { LauncherAppState app = LauncherAppState.getInstance(); @@ -150,37 +116,41 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { mIsAppWidget = true; mInfo = info; - final ImageView image = (ImageView) findViewById(R.id.widget_preview); if (maxWidth > -1) { - image.setMaxWidth(maxWidth); - } - final TextView name = (TextView) findViewById(R.id.widget_name); - name.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info)); - final TextView dims = (TextView) findViewById(R.id.widget_dims); - if (dims != null) { - int hSpan = Math.min(info.spanX, (int) grid.numColumns); - int vSpan = Math.min(info.spanY, (int) grid.numRows); - dims.setText(String.format(mDimensionsFormatString, hSpan, vSpan)); + mWidgetImage.setMaxWidth(maxWidth); } + // TODO(hyunyoungs): setup a cache for these labels. + mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info)); + int hSpan = Math.min(info.spanX, (int) grid.numColumns); + int vSpan = Math.min(info.spanY, (int) grid.numRows); + mWidgetDims.setText(String.format(mDimensionsFormatString, hSpan, vSpan)); mWidgetPreviewLoader = loader; } + /** + * Apply the resolve info to the view. + */ public void applyFromResolveInfo( PackageManager pm, ResolveInfo info, WidgetPreviewLoader loader) { mIsAppWidget = false; mInfo = info; CharSequence label = info.loadLabel(pm); - final TextView name = (TextView) findViewById(R.id.widget_name); - name.setText(label); - final TextView dims = (TextView) findViewById(R.id.widget_dims); - if (dims != null) { - dims.setText(String.format(mDimensionsFormatString, 1, 1)); - } + mWidgetName.setText(label); + mWidgetDims.setText(String.format(mDimensionsFormatString, 1, 1)); mWidgetPreviewLoader = loader; } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + deletePreview(false); + + if (DEBUG) { + Log.d(TAG, String.format("[tag=%s] onDetachedFromWindow", getTagToString())); + } + } + public int[] getPreviewSize() { - final ImageView i = (ImageView) findViewById(R.id.widget_preview); int[] maxSize = new int[2]; maxSize[0] = mPresetPreviewSize; maxSize[1] = mPresetPreviewSize; @@ -189,110 +159,28 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { public void applyPreview(Bitmap bitmap) { FastBitmapDrawable preview = new FastBitmapDrawable(bitmap); - final WidgetImageView image = - (WidgetImageView) findViewById(R.id.widget_preview); if (DEBUG) { Log.d(TAG, String.format("[tag=%s] applyPreview preview: %s", getTagToString(), preview)); } if (preview != null) { - image.mAllowRequestLayout = false; - image.setImageDrawable(preview); + mWidgetImage.setImageDrawable(preview); if (mIsAppWidget) { // center horizontally int[] imageSize = getPreviewSize(); int centerAmount = (imageSize[0] - preview.getIntrinsicWidth()) / 2; - image.setPadding(mOriginalImagePadding.left + centerAmount, + mWidgetImage.setPadding(mOriginalImagePadding.left + centerAmount, mOriginalImagePadding.top, mOriginalImagePadding.right, mOriginalImagePadding.bottom); } - image.setAlpha(0f); - image.animate().alpha(1.0f).setDuration(FADE_IN_DURATION_MS); - image.mAllowRequestLayout = true; - image.requestLayout(); - } - } - - void setShortPressListener(ShortPressListener listener) { - mShortPressListener = listener; - } - - interface ShortPressListener { - void onShortPress(View v); - void cleanUpShortPress(View v); - } - - class CheckForShortPress implements Runnable { - public void run() { - if (sShortpressTarget != null) return; - if (mShortPressListener != null) { - mShortPressListener.onShortPress(WidgetCell.this); - sShortpressTarget = WidgetCell.this; - } - mShortPressTriggered = true; + mWidgetImage.setAlpha(0f); + mWidgetImage.animate().alpha(1.0f).setDuration(FADE_IN_DURATION_MS); + // TODO(hyunyoungs): figure out why this has to be called explicitly. + mWidgetImage.requestLayout(); } } - private void checkForShortPress() { - if (sShortpressTarget != null) return; - if (mPendingCheckForShortPress == null) { - mPendingCheckForShortPress = new CheckForShortPress(); - } - postDelayed(mPendingCheckForShortPress, 120); - } - - /** - * Remove the longpress detection timer. - */ - private void removeShortPressCallback() { - if (mPendingCheckForShortPress != null) { - removeCallbacks(mPendingCheckForShortPress); - } - } - - private void cleanUpShortPress() { - removeShortPressCallback(); - if (mShortPressTriggered) { - if (mShortPressListener != null) { - mShortPressListener.cleanUpShortPress(WidgetCell.this); - } - mShortPressTriggered = false; - } - } - - static void resetShortPressTarget() { - sShortpressTarget = null; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - super.onTouchEvent(event); - - switch (event.getAction()) { - case MotionEvent.ACTION_UP: - cleanUpShortPress(); - break; - case MotionEvent.ACTION_DOWN: - checkForShortPress(); - break; - case MotionEvent.ACTION_CANCEL: - cleanUpShortPress(); - break; - case MotionEvent.ACTION_MOVE: - break; - } - - // We eat up the touch events here, since the PagedView (which uses the same swiping - // touch code as Workspace previously) uses onInterceptTouchEvent() to determine when - // the user is scrolling between pages. This means that if the pages themselves don't - // handle touch events, it gets forwarded up to PagedView itself, and it's own - // onTouchEvent() handling will prevent further intercept touch events from being called - // (it's the same view in that case). This is not ideal, but to prevent more changes, - // we just always mark the touch event as handled. - return true; - } - public void ensurePreview() { if (mActiveRequest != null) { return; @@ -331,6 +219,16 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { return Math.min(size[0], info.spanX * cellWidth); } + + private void deletePreview(boolean recycleImage) { + mWidgetImage.setImageDrawable(null); + + if (mActiveRequest != null) { + mActiveRequest.cancel(recycleImage); + mActiveRequest = null; + } + } + /** * Helper method to get the string info of the tag. */ diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java new file mode 100644 index 000000000..d65455053 --- /dev/null +++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java @@ -0,0 +1,197 @@ +package com.android.launcher3.widget; + +import android.appwidget.AppWidgetHostView; +import android.appwidget.AppWidgetManager; +import android.content.Context; +import android.graphics.Rect; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import android.view.View; + +import com.android.launcher3.AppWidgetResizeFrame; +import com.android.launcher3.DragLayer; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.compat.AppWidgetManagerCompat; + +public class WidgetHostViewLoader { + + private static final boolean DEBUG = false; + private static final String TAG = "WidgetHostViewLoader"; + + /* constants used for widget loading state. */ + private static final int WIDGET_NO_CLEANUP_REQUIRED = -1; + private static final int WIDGET_PRELOAD_PENDING = 0; + private static final int WIDGET_BOUND = 1; + private static final int WIDGET_INFLATED = 2; + + int mState = WIDGET_NO_CLEANUP_REQUIRED; + + /* Runnables to handle inflation and binding. */ + private Runnable mInflateWidgetRunnable = null; + private Runnable mBindWidgetRunnable = null; + + /* Id of the widget being handled. */ + int mWidgetLoadingId = -1; + PendingAddWidgetInfo mCreateWidgetInfo = null; + + // TODO: technically, this class should not have to know the existence of the launcher. + private Launcher mLauncher; + private Handler mHandler; + + public WidgetHostViewLoader(Launcher launcher) { + mLauncher = launcher; + mHandler = new Handler(); + } + + /** + * Start loading the widget. + */ + public void load(View v) { + if (mCreateWidgetInfo != null) { + // Just in case the cleanup process wasn't properly executed. + finish(false); + } + boolean status = false; + if (v.getTag() instanceof PendingAddWidgetInfo) { + mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag()); + status = preloadWidget(v, mCreateWidgetInfo); + } + if (DEBUG) { + Log.d(TAG, String.format("load started on [state=%d, status=%s]", mState, status)); + } + } + + + /** + * Clean up according to what the last known state was. + * @param widgetIdUsed {@code true} if the widgetId was consumed which can happen only + * when view is fully inflated + */ + public void finish(boolean widgetIdUsed) { + if (DEBUG) { + Log.d(TAG, String.format("cancel on state [%d] widgetId=[%d]", + mState, mWidgetLoadingId)); + } + + // If the widget was not added, we may need to do further cleanup. + PendingAddWidgetInfo info = mCreateWidgetInfo; + mCreateWidgetInfo = null; + + if (mState == WIDGET_PRELOAD_PENDING) { + // We never did any preloading, so just remove pending callbacks to do so + mHandler.removeCallbacks(mBindWidgetRunnable); + mHandler.removeCallbacks(mInflateWidgetRunnable); + } else if (mState == WIDGET_BOUND) { + // Delete the widget id which was allocated + if (mWidgetLoadingId != -1 && !info.isCustomWidget()) { + mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId); + } + + // We never got around to inflating the widget, so remove the callback to do so. + mHandler.removeCallbacks(mInflateWidgetRunnable); + } else if (mState == WIDGET_INFLATED && !widgetIdUsed) { + // Delete the widget id which was allocated + if (mWidgetLoadingId != -1 && !info.isCustomWidget()) { + mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId); + } + + // The widget was inflated and added to the DragLayer -- remove it. + AppWidgetHostView widget = info.boundWidget; + mLauncher.getDragLayer().removeView(widget); + } + setState(WIDGET_NO_CLEANUP_REQUIRED); + mWidgetLoadingId = -1; + } + + private boolean preloadWidget(final View v, final PendingAddWidgetInfo info) { + final LauncherAppWidgetProviderInfo pInfo = info.info; + + final Bundle options = pInfo.isCustomWidget ? null : + getDefaultOptionsForWidget(mLauncher, info); + + // If there is a configuration activity, do not follow thru bound and inflate. + if (pInfo.configure != null) { + info.bindOptions = options; + return false; + } + setState(WIDGET_PRELOAD_PENDING); + mBindWidgetRunnable = new Runnable() { + @Override + public void run() { + if (pInfo.isCustomWidget) { + setState(WIDGET_BOUND); + return; + } + + mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId(); + if(AppWidgetManagerCompat.getInstance(mLauncher).bindAppWidgetIdIfAllowed( + mWidgetLoadingId, pInfo, options)) { + setState(WIDGET_BOUND); + } + } + }; + mHandler.post(mBindWidgetRunnable); + + mInflateWidgetRunnable = new Runnable() { + @Override + public void run() { + if (mState != WIDGET_BOUND) { + return; + } + AppWidgetHostView hostView = mLauncher.getAppWidgetHost().createView( + (Context) mLauncher, mWidgetLoadingId, pInfo); + info.boundWidget = hostView; + setState(WIDGET_INFLATED); + hostView.setVisibility(View.INVISIBLE); + int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info, false); + + // We want the first widget layout to be the correct size. This will be important + // for width size reporting to the AppWidgetManager. + DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0], + unScaledSize[1]); + lp.x = lp.y = 0; + lp.customPosition = true; + hostView.setLayoutParams(lp); + mLauncher.getDragLayer().addView(hostView); + v.setTag(info); + } + }; + mHandler.post(mInflateWidgetRunnable); + return true; + } + + 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) { + AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, rect); + Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher, + info.componentName, null); + + float density = launcher.getResources().getDisplayMetrics().density; + int xPaddingDips = (int) ((padding.left + padding.right) / density); + int yPaddingDips = (int) ((padding.top + padding.bottom) / density); + + options = new Bundle(); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, + rect.left - xPaddingDips); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, + rect.top - yPaddingDips); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, + rect.right - xPaddingDips); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, + rect.bottom - yPaddingDips); + } + return options; + } + + private void setState(int state) { + if (DEBUG) { + Log.d(TAG, String.format(" state [%d -> %d]", mState, state)); + } + mState = state; + } +} diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 292a5de20..27a3ea13d 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -30,6 +30,7 @@ import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.Toast; import com.android.launcher3.CellLayout; import com.android.launcher3.DeleteDropTarget; @@ -54,15 +55,17 @@ import java.util.ArrayList; /** * The widgets list view container. */ -public class WidgetsContainerView extends FrameLayout implements Insettable, View.OnTouchListener, - View.OnLongClickListener, DragSource{ +public class WidgetsContainerView extends FrameLayout implements Insettable, + View.OnLongClickListener, View.OnClickListener, DragSource{ - private static final String TAG = "WidgetContainerView"; + private static final String TAG = "WidgetsContainerView"; private static final boolean DEBUG = false; /* {@link RecyclerView} will keep following # of views in cache, before recycling. */ private static final int WIDGET_CACHE_SIZE = 2; + private static final int SPRING_MODE_DELAY_MS = 150; + /* Global instances that are used inside this container. */ private Launcher mLauncher; private DragController mDragController; @@ -75,12 +78,13 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie private RecyclerView mView; private WidgetsListAdapter mAdapter; - /* Dragging related. */ - private boolean mDraggingWidget = false; // TODO(hyunyoungs): seems not needed? check! - private Point mLastTouchDownPos = new Point(); + /* Touch handling related member variables. */ + private Toast mWidgetInstructionToast; /* Rendering related. */ private WidgetPreviewLoader mWidgetPreviewLoader; + private WidgetHostViewLoader mWidgetHostViewLoader; + private Rect mPadding = new Rect(); public WidgetsContainerView(Context context) { @@ -95,8 +99,8 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie super(context, attrs, defStyleAttr); mLauncher = (Launcher) context; mDragController = mLauncher.getDragController(); - - mAdapter = new WidgetsListAdapter(context, this, mLauncher, this, mLauncher); + mWidgetHostViewLoader = new WidgetHostViewLoader(mLauncher); + mAdapter = new WidgetsListAdapter(context, this, this, mLauncher); mWidgets = new WidgetsModel(context, mAdapter); mAdapter.setWidgetsModel(mWidgets); mIconCache = (LauncherAppState.getInstance()).getIconCache(); @@ -147,11 +151,26 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie // @Override + public void onClick(View v) { + // When we have exited widget tray or are in transition, disregard clicks + if (!mLauncher.isWidgetsViewVisible() + || mLauncher.getWorkspace().isSwitchingState() + || !(v instanceof WidgetCell)) return; + + // Let the user know that they have to long press to add a widget + if (mWidgetInstructionToast != null) { + mWidgetInstructionToast.cancel(); + } + mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add, + Toast.LENGTH_SHORT); + mWidgetInstructionToast.show(); + } + + @Override public boolean onLongClick(View v) { if (DEBUG) { Log.d(TAG, String.format("onLonglick [v=%s]", v)); } - // Return early if this is not initiated from a touch if (!v.isInTouchMode()) return false; // When we have exited all apps or are in transition, disregard long clicks @@ -161,7 +180,11 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie Log.d(TAG, String.format("onLonglick dragging enabled?.", v)); if (!mLauncher.isDraggingEnabled()) return false; - return beginDragging(v); + boolean status = beginDragging(v); + if (status) { + mWidgetHostViewLoader.load(v); + } + return status; } private boolean beginDragging(View v) { @@ -174,7 +197,7 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie } // We delay entering spring-loaded mode slightly to make sure the UI - // thready is free of any work. + // thread is free of any work. postDelayed(new Runnable() { @Override public void run() { @@ -184,13 +207,12 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie mLauncher.enterSpringLoadedDragMode(); } } - }, 150); + }, SPRING_MODE_DELAY_MS); return true; } private boolean beginDraggingWidget(WidgetCell v) { - mDraggingWidget = true; // Get the widget preview as the drag representation ImageView image = (ImageView) v.findViewById(R.id.widget_preview); PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag(); @@ -198,7 +220,6 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and // we abort the drag. if (image.getDrawable() == null) { - mDraggingWidget = false; return false; } @@ -259,19 +280,6 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie return true; } - /* - * @see android.view.View.OnTouchListener#onTouch(android.view.View, android.view.MotionEvent) - */ - @Override - public boolean onTouch(View v, MotionEvent ev) { - Log.d(TAG, String.format("onTouch [MotionEvent=%s]", ev)); - if (ev.getAction() == MotionEvent.ACTION_DOWN || - ev.getAction() == MotionEvent.ACTION_MOVE) { - mLastTouchDownPos.set((int) ev.getX(), (int) ev.getY()); - } - return false; - } - // // Drag related handling methods that implement {@link DragSource} interface. // @@ -340,6 +348,10 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie } d.deferDragViewCleanupPostAnimation = false; } + //TODO(hyunyoungs): if drop fails, this call cleans up correctly. + // However, in rare corner case where drop succeeds but doesn't end up using the widget + // id created by the loader, this finish will leave dangling widget id. + mWidgetHostViewLoader.finish(success); } // @@ -368,5 +380,4 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie } return mWidgetPreviewLoader; } - }
\ No newline at end of file diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java index afeb2d385..f6ab21eb4 100644 --- a/src/com/android/launcher3/widget/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java @@ -56,20 +56,16 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { private WidgetsModel mWidgetsModel; private WidgetPreviewLoader mWidgetPreviewLoader; - private View.OnTouchListener mTouchListener; private View.OnClickListener mIconClickListener; private View.OnLongClickListener mIconLongClickListener; - public WidgetsListAdapter(Context context, - View.OnTouchListener touchListener, View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener, Launcher launcher) { mLayoutInflater = LayoutInflater.from(context); mContext = context; - mTouchListener = touchListener; mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; @@ -109,7 +105,6 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { // set up touch. widget.setOnClickListener(mIconClickListener); widget.setOnLongClickListener(mIconLongClickListener); - widget.setOnTouchListener(mTouchListener); row.addView(widget); } } else if (diff < 0) { |