diff options
Diffstat (limited to 'src')
73 files changed, 1927 insertions, 1285 deletions
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java index e0694f3cf..4c4d67c59 100644 --- a/src/com/android/launcher3/AppInfo.java +++ b/src/com/android/launcher3/AppInfo.java @@ -62,7 +62,7 @@ public class AppInfo extends ItemInfo { */ int isDisabled = ShortcutInfo.DEFAULT; - AppInfo() { + public AppInfo() { itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; } @@ -125,11 +125,8 @@ public class AppInfo extends ItemInfo { } @Override - public String toString() { - return "ApplicationInfo(title=" + title + " id=" + this.id - + " type=" + this.itemType + " container=" + this.container - + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY - + " spanX=" + spanX + " spanY=" + spanY + " user=" + user + ")"; + protected String dumpProperties() { + return super.dumpProperties() + " componentName=" + componentName; } /** diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index 0d5043bb2..d5309b4f0 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -316,7 +316,7 @@ public class AutoInstallsLayout { parsers.put(TAG_APP_ICON, new AppShortcutParser()); parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser()); parsers.put(TAG_FOLDER, new FolderParser()); - parsers.put(TAG_APPWIDGET, new AppWidgetParser()); + parsers.put(TAG_APPWIDGET, new PendingWidgetParser()); parsers.put(TAG_SHORTCUT, new ShortcutParser(mSourceRes)); return parsers; } @@ -459,8 +459,12 @@ public class AutoInstallsLayout { /** * AppWidget parser: Required attributes packageName, className, spanX and spanY. * Options child nodes: <extra key=... value=... /> + * It adds a pending widget which allows the widget to come later. If there are extras, those + * are passed to widget options during bind. + * The config activity for the widget (if present) is not shown, so any optional configurations + * should be passed as extras and the widget should support reading these widget options. */ - protected class AppWidgetParser implements TagParser { + protected class PendingWidgetParser implements TagParser { @Override public long parseAndAdd(XmlResourceParser parser) @@ -468,27 +472,13 @@ public class AutoInstallsLayout { final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME); final String className = getAttributeValue(parser, ATTR_CLASS_NAME); if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) { - if (LOGD) Log.d(TAG, "Skipping invalid <favorite> with no component"); + if (LOGD) Log.d(TAG, "Skipping invalid <appwidget> with no component"); return -1; } - ComponentName cn = new ComponentName(packageName, className); - try { - mPackageManager.getReceiverInfo(cn, 0); - } catch (Exception e) { - String[] packages = mPackageManager.currentToCanonicalPackageNames( - new String[] { packageName }); - cn = new ComponentName(packages[0], className); - try { - mPackageManager.getReceiverInfo(cn, 0); - } catch (Exception e1) { - if (LOGD) Log.d(TAG, "Can't find widget provider: " + className); - return -1; - } - } - mValues.put(Favorites.SPANX, getAttributeValue(parser, ATTR_SPAN_X)); mValues.put(Favorites.SPANY, getAttributeValue(parser, ATTR_SPAN_Y)); + mValues.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); // Read the extras Bundle extras = new Bundle(); @@ -513,38 +503,26 @@ public class AutoInstallsLayout { } } - final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); - long insertedId = -1; - try { - int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); - - if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn)) { - if (LOGD) Log.e(TAG, "Unable to bind app widget id " + cn); - return -1; - } + return verifyAndInsert(new ComponentName(packageName, className), extras); + } - mValues.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); - mValues.put(Favorites.APPWIDGET_ID, appWidgetId); - mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString()); - mValues.put(Favorites._ID, mCallback.generateNewItemId()); - insertedId = mCallback.insertAndCheck(mDb, mValues); - if (insertedId < 0) { - mAppWidgetHost.deleteAppWidgetId(appWidgetId); - return insertedId; - } + protected long verifyAndInsert(ComponentName cn, Bundle extras) { + mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString()); + mValues.put(Favorites.RESTORED, + LauncherAppWidgetInfo.FLAG_ID_NOT_VALID | + LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY | + LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG); + mValues.put(Favorites._ID, mCallback.generateNewItemId()); + if (!extras.isEmpty()) { + mValues.put(Favorites.INTENT, new Intent().putExtras(extras).toUri(0)); + } - // Send a broadcast to configure the widget - if (!extras.isEmpty()) { - Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE); - intent.setComponent(cn); - intent.putExtras(extras); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - mContext.sendBroadcast(intent); - } - } catch (RuntimeException ex) { - if (LOGD) Log.e(TAG, "Problem allocating appWidgetId", ex); + long insertedId = mCallback.insertAndCheck(mDb, mValues); + if (insertedId < 0) { + return -1; + } else { + return insertedId; } - return insertedId; } } diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 33b3ad347..a294fa538 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -65,6 +65,7 @@ public class BubbleTextView extends TextView private static final int DISPLAY_WORKSPACE = 0; private static final int DISPLAY_ALL_APPS = 1; + private static final int DISPLAY_FOLDER = 2; private final Launcher mLauncher; private Drawable mIcon; @@ -125,12 +126,13 @@ public class BubbleTextView extends TextView setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx); setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx); defaultIconSize = grid.allAppsIconSizePx; + } else if (display == DISPLAY_FOLDER) { + setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx); } mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false); mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride, defaultIconSize); - a.recycle(); if (mCustomShadowsEnabled) { diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java index 0f6073eee..cf8abae2e 100644 --- a/src/com/android/launcher3/ButtonDropTarget.java +++ b/src/com/android/launcher3/ButtonDropTarget.java @@ -43,6 +43,7 @@ import android.widget.TextView; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.util.Thunk; @@ -199,8 +200,8 @@ public abstract class ButtonDropTarget extends TextView } @Override - public void onDragStart(DragSource source, ItemInfo info, int dragAction) { - mActive = supportsDrop(source, info); + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { + mActive = supportsDrop(dragObject.dragSource, dragObject.dragInfo); mDrawable.setColorFilter(null); if (mCurrentColorAnim != null) { mCurrentColorAnim.cancel(); @@ -209,6 +210,9 @@ public abstract class ButtonDropTarget extends TextView setTextColor(mOriginalTextColor); (mHideParentOnDisable ? ((ViewGroup) getParent()) : this) .setVisibility(mActive ? View.VISIBLE : View.GONE); + + mAccessibleDrag = options.isAccessibleDrag; + setOnClickListener(mAccessibleDrag ? this : null); } @Override @@ -227,6 +231,7 @@ public abstract class ButtonDropTarget extends TextView @Override public void onDragEnd() { mActive = false; + setOnClickListener(null); } /** @@ -254,7 +259,8 @@ public abstract class ButtonDropTarget extends TextView } }; dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f, - DRAG_VIEW_DROP_DURATION, new DecelerateInterpolator(2), + mLauncher.getDragController().isExternalDrag() ? 1 : DRAG_VIEW_DROP_DURATION, + new DecelerateInterpolator(2), new LinearInterpolator(), onAnimationEndRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null); } @@ -308,11 +314,6 @@ public abstract class ButtonDropTarget extends TextView return to; } - public void enableAccessibleDrag(boolean enable) { - mAccessibleDrag = enable; - setOnClickListener(enable ? this : null); - } - @Override public void onClick(View v) { mLauncher.getAccessibilityDelegate().handleAccessibleDrop(this, null, null); diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 77f6612c1..6714d9f17 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -54,6 +54,7 @@ import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.graphics.DragPreviewProvider; import com.android.launcher3.util.CellAndSpan; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.ParcelableSparseArray; @@ -235,6 +236,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { for (int i = 0; i < mDragOutlines.length; i++) { mDragOutlines[i] = new Rect(-1, -1, -1, -1); } + mDragOutlinePaint.setColor(getResources().getColor(R.color.outline_color)); // When dragging things around the home screens, we show a green outline of // where the item will land. The outlines gradually fade out, leaving a trail @@ -1047,15 +1049,16 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { return false; } - void visualizeDropLocation(View v, Bitmap dragOutline, int cellX, int cellY, int spanX, - int spanY, boolean resize, DropTarget.DragObject dragObject) { + void visualizeDropLocation(View v, DragPreviewProvider outlineProvider, int cellX, int cellY, + int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) { final int oldDragCellX = mDragCell[0]; final int oldDragCellY = mDragCell[1]; - if (dragOutline == null && v == null) { + if (outlineProvider == null || outlineProvider.gerenatedDragOutline == null) { return; } + Bitmap dragOutline = outlineProvider.gerenatedDragOutline; if (cellX != oldDragCellX || cellY != oldDragCellY) { Point dragOffset = dragObject.dragView.getDragVisualizeOffset(); Rect dragRegion = dragObject.dragView.getDragRegion(); @@ -1354,12 +1357,9 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { // and that passed in. int curDirectionScore = direction[0] * curDirection[0] + direction[1] * curDirection[1]; - boolean exactDirectionOnly = false; - boolean directionMatches = direction[0] == curDirection[0] && - direction[0] == curDirection[0]; - if ((directionMatches || !exactDirectionOnly) && - Float.compare(distance, bestDistance) < 0 || (Float.compare(distance, - bestDistance) == 0 && curDirectionScore > bestDirectionScore)) { + if (Float.compare(distance, bestDistance) < 0 || + (Float.compare(distance, bestDistance) == 0 + && curDirectionScore > bestDirectionScore)) { bestDistance = distance; bestDirectionScore = curDirectionScore; bestXY[0] = x; diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java index e6f34d952..ef28d1e8c 100644 --- a/src/com/android/launcher3/DefaultLayoutParser.java +++ b/src/com/android/launcher3/DefaultLayoutParser.java @@ -1,6 +1,8 @@ package com.android.launcher3; import android.appwidget.AppWidgetHost; +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -9,6 +11,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.os.Bundle; import android.text.TextUtils; import android.util.Log; @@ -42,6 +45,10 @@ public class DefaultLayoutParser extends AutoInstallsLayout { private static final String ATTR_SCREEN = "screen"; private static final String ATTR_FOLDER_ITEMS = "folderItems"; + // TODO: Remove support for this broadcast, instead use widget options to send bind time options + private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE = + "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE"; + public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost, LayoutParserCallback callback, Resources sourceRes, int layoutId) { super(context, appWidgetHost, callback, sourceRes, layoutId, TAG_FAVORITES); @@ -270,4 +277,61 @@ public class DefaultLayoutParser extends AutoInstallsLayout { return super.parseAndAdd(parser); } } + + + /** + * AppWidget parser which enforces that the app is already installed when the layout is parsed. + */ + protected class AppWidgetParser extends PendingWidgetParser { + + @Override + protected long verifyAndInsert(ComponentName cn, Bundle extras) { + try { + mPackageManager.getReceiverInfo(cn, 0); + } catch (Exception e) { + String[] packages = mPackageManager.currentToCanonicalPackageNames( + new String[] { cn.getPackageName() }); + cn = new ComponentName(packages[0], cn.getClassName()); + try { + mPackageManager.getReceiverInfo(cn, 0); + } catch (Exception e1) { + Log.d(TAG, "Can't find widget provider: " + cn.getClassName()); + return -1; + } + } + + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); + long insertedId = -1; + try { + int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); + + if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn)) { + Log.e(TAG, "Unable to bind app widget id " + cn); + mAppWidgetHost.deleteAppWidgetId(appWidgetId); + return -1; + } + + mValues.put(Favorites.APPWIDGET_ID, appWidgetId); + mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString()); + mValues.put(Favorites._ID, mCallback.generateNewItemId()); + insertedId = mCallback.insertAndCheck(mDb, mValues); + if (insertedId < 0) { + mAppWidgetHost.deleteAppWidgetId(appWidgetId); + return insertedId; + } + + // Send a broadcast to configure the widget + if (!extras.isEmpty()) { + Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE); + intent.setComponent(cn); + intent.putExtras(extras); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + mContext.sendBroadcast(intent); + } + } catch (RuntimeException ex) { + Log.e(TAG, "Problem allocating appWidgetId", ex); + } + return insertedId; + } + } } diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java index f24e00b5c..705f84101 100644 --- a/src/com/android/launcher3/DeleteDropTarget.java +++ b/src/com/android/launcher3/DeleteDropTarget.java @@ -25,6 +25,7 @@ import android.view.View; import android.view.animation.AnimationUtils; import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.folder.Folder; import com.android.launcher3.util.FlingAnimation; import com.android.launcher3.util.Thunk; @@ -49,9 +50,9 @@ public class DeleteDropTarget extends ButtonDropTarget { } @Override - public void onDragStart(DragSource source, ItemInfo info, int dragAction) { - super.onDragStart(source, info, dragAction); - setTextBasedOnDragSource(source); + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { + super.onDragStart(dragObject, options); + setTextBasedOnDragSource(dragObject.dragSource); } /** @return true for items that should have a "Remove" action in accessibility. */ diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index c9fd85a16..c4e6ed119 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -20,8 +20,6 @@ import android.appwidget.AppWidgetHostView; import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; -import android.graphics.Paint; -import android.graphics.Paint.FontMetrics; import android.graphics.Point; import android.graphics.Rect; import android.util.DisplayMetrics; @@ -100,6 +98,7 @@ public class DeviceProfile { public int folderIconPreviewPadding; public int folderCellWidthPx; public int folderCellHeightPx; + public int folderChildDrawablePaddingPx; // Hotseat public int hotseatCellWidthPx; @@ -255,12 +254,9 @@ public class DeviceProfile { allAppsIconDrawablePaddingPx = iconDrawablePaddingPx; allAppsIconTextSizePx = iconTextSizePx; - // Calculate the actual text height - Paint textPaint = new Paint(); - textPaint.setTextSize(iconTextSizePx); - FontMetrics fm = textPaint.getFontMetrics(); cellWidthPx = iconSizePx; - cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top); + cellHeightPx = iconSizePx + iconDrawablePaddingPx + + Utilities.calculateTextHeight(iconTextSizePx); final float scaleDps = !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? 0f : res.getDimensionPixelSize(R.dimen.dragViewScale); dragViewScale = (iconSizePx + scaleDps) / iconSizePx; @@ -281,12 +277,25 @@ public class DeviceProfile { res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f; } - // Folder - int folderCellPadding = isTablet || isLandscape ? 6 * edgeMarginPx : 3 * edgeMarginPx; + // Folder cell + int cellPaddingX = res.getDimensionPixelSize(R.dimen.folder_cell_x_padding); + int cellPaddingY = res.getDimensionPixelSize(R.dimen.folder_cell_y_padding); + final int folderChildTextSize = + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_child_text_size)); + + final int folderBottomPanelSize = + 2 * res.getDimensionPixelSize(R.dimen.folder_label_padding) + + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size)); + // Don't let the folder get too close to the edges of the screen. - folderCellWidthPx = Math.min(cellWidthPx + folderCellPadding, + folderCellWidthPx = Math.min(iconSizePx + 2 * cellPaddingX, (availableWidthPx - 4 * edgeMarginPx) / inv.numFolderColumns); - folderCellHeightPx = cellHeightPx + edgeMarginPx; + folderCellHeightPx = Math.min(iconSizePx + 3 * cellPaddingY + folderChildTextSize, + (availableHeightPx - 4 * edgeMarginPx - folderBottomPanelSize) / inv.numFolderRows); + folderChildDrawablePaddingPx = Math.max(0, + (folderCellHeightPx - iconSizePx - folderChildTextSize) / 3); + + // Folder icon folderBackgroundOffset = -edgeMarginPx; folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset; folderIconPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); @@ -296,9 +305,6 @@ public class DeviceProfile { mInsets.set(insets); } - /** - * @param recyclerViewWidth the available width of the AllAppsRecyclerView - */ public void updateAppsViewNumCols() { allAppsNumCols = allAppsNumPredictiveCols = inv.numColumns; } diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java index 5966af51c..42bab4705 100644 --- a/src/com/android/launcher3/DropTargetBar.java +++ b/src/com/android/launcher3/DropTargetBar.java @@ -27,6 +27,7 @@ import android.view.animation.AccelerateInterpolator; import android.widget.LinearLayout; import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragOptions; /* * The top bar containing various drop targets: Delete/App Info/Uninstall. @@ -120,17 +121,11 @@ public class DropTargetBar extends LinearLayout implements DragController.DragLi } } - public void enableAccessibleDrag(boolean enable) { - mDeleteDropTarget.enableAccessibleDrag(enable); - mAppInfoDropTarget.enableAccessibleDrag(enable); - mUninstallDropTarget.enableAccessibleDrag(enable); - } - /* * DragController.DragListener implementation */ @Override - public void onDragStart(DragSource source, ItemInfo info, int dragAction) { + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { animateToVisibility(true); } diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java index bf4551b26..c06f727a5 100644 --- a/src/com/android/launcher3/ExtendedEditText.java +++ b/src/com/android/launcher3/ExtendedEditText.java @@ -19,6 +19,7 @@ import android.content.Context; import android.util.AttributeSet; import android.view.DragEvent; import android.view.KeyEvent; +import android.view.inputmethod.InputMethodManager; import android.widget.EditText; @@ -27,6 +28,8 @@ import android.widget.EditText; */ public class ExtendedEditText extends EditText { + private boolean mShowImeAfterFirstLayout; + /** * Implemented by listeners of the back key. */ @@ -37,10 +40,12 @@ public class ExtendedEditText extends EditText { private OnBackKeyListener mBackKeyListener; public ExtendedEditText(Context context) { + // ctor chaining breaks the touch handling super(context); } public ExtendedEditText(Context context, AttributeSet attrs) { + // ctor chaining breaks the touch handling super(context, attrs); } @@ -69,4 +74,29 @@ public class ExtendedEditText extends EditText { // We don't want this view to interfere with Launcher own drag and drop. return false; } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (mShowImeAfterFirstLayout) { + // soft input only shows one frame after the layout of the EditText happens, + post(new Runnable() { + @Override + public void run() { + showSoftInput(); + mShowImeAfterFirstLayout = false; + } + }); + } + } + + public void showKeyboard() { + mShowImeAfterFirstLayout = !showSoftInput(); + } + + private boolean showSoftInput() { + return requestFocus() && + ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE)) + .showSoftInput(this, InputMethodManager.SHOW_IMPLICIT); + } } diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index 9a9985211..c0a8caaa3 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -126,14 +126,6 @@ public class FolderInfo extends ItemInfo { public void onItemsChanged(boolean animate); } - @Override - public String toString() { - return "FolderInfo(id=" + this.id + " type=" + this.itemType - + " container=" + this.container + " screen=" + screenId - + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX - + " spanY=" + spanY + ")"; - } - public boolean hasOption(int optionFlag) { return (options & optionFlag) != 0; } diff --git a/src/com/android/launcher3/HolographicOutlineHelper.java b/src/com/android/launcher3/HolographicOutlineHelper.java index 427acea76..9dec7d9e4 100644 --- a/src/com/android/launcher3/HolographicOutlineHelper.java +++ b/src/com/android/launcher3/HolographicOutlineHelper.java @@ -29,6 +29,10 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.SparseArray; +import com.android.launcher3.config.ProviderConfig; + +import java.nio.ByteBuffer; + /** * Utility class to generate shadow and outline effect, which are used for click feedback * and drag-n-drop respectively. @@ -79,50 +83,53 @@ public class HolographicOutlineHelper { * Applies a more expensive and accurate outline to whatever is currently drawn in a specified * bitmap. */ - public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, - int outlineColor) { - applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, true); + public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas) { + applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, true); } - void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, - int outlineColor, boolean clipAlpha) { + public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, + boolean clipAlpha) { + if (ProviderConfig.IS_DOGFOOD_BUILD && srcDst.getConfig() != Bitmap.Config.ALPHA_8) { + throw new RuntimeException("Outline blue is only supported on alpha bitmaps"); + } // We start by removing most of the alpha channel so as to ignore shadows, and // other types of partial transparency when defining the shape of the object if (clipAlpha) { - int[] srcBuffer = new int[srcDst.getWidth() * srcDst.getHeight()]; - srcDst.getPixels(srcBuffer, - 0, srcDst.getWidth(), 0, 0, srcDst.getWidth(), srcDst.getHeight()); - for (int i = 0; i < srcBuffer.length; i++) { - final int alpha = srcBuffer[i] >>> 24; - if (alpha < 188) { - srcBuffer[i] = 0; + byte[] pixels = new byte[srcDst.getWidth() * srcDst.getHeight()]; + ByteBuffer buffer = ByteBuffer.wrap(pixels); + buffer.rewind(); + srcDst.copyPixelsToBuffer(buffer); + + for (int i = 0; i < pixels.length; i++) { + if ((pixels[i] & 0xFF) < 188) { + pixels[i] = 0; } } - srcDst.setPixels(srcBuffer, - 0, srcDst.getWidth(), 0, 0, srcDst.getWidth(), srcDst.getHeight()); + + buffer.rewind(); + srcDst.copyPixelsFromBuffer(buffer); } - Bitmap glowShape = srcDst.extractAlpha(); // calculate the outer blur first mBlurPaint.setMaskFilter(mMediumOuterBlurMaskFilter); int[] outerBlurOffset = new int[2]; - Bitmap thickOuterBlur = glowShape.extractAlpha(mBlurPaint, outerBlurOffset); + Bitmap thickOuterBlur = srcDst.extractAlpha(mBlurPaint, outerBlurOffset); mBlurPaint.setMaskFilter(mThinOuterBlurMaskFilter); int[] brightOutlineOffset = new int[2]; - Bitmap brightOutline = glowShape.extractAlpha(mBlurPaint, brightOutlineOffset); + Bitmap brightOutline = srcDst.extractAlpha(mBlurPaint, brightOutlineOffset); // calculate the inner blur - srcDstCanvas.setBitmap(glowShape); + srcDstCanvas.setBitmap(srcDst); srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT); mBlurPaint.setMaskFilter(mMediumInnerBlurMaskFilter); int[] thickInnerBlurOffset = new int[2]; - Bitmap thickInnerBlur = glowShape.extractAlpha(mBlurPaint, thickInnerBlurOffset); + Bitmap thickInnerBlur = srcDst.extractAlpha(mBlurPaint, thickInnerBlurOffset); // mask out the inner blur srcDstCanvas.setBitmap(thickInnerBlur); - srcDstCanvas.drawBitmap(glowShape, -thickInnerBlurOffset[0], + srcDstCanvas.drawBitmap(srcDst, -thickInnerBlurOffset[0], -thickInnerBlurOffset[1], mErasePaint); srcDstCanvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(), mErasePaint); @@ -132,14 +139,12 @@ public class HolographicOutlineHelper { // draw the inner and outer blur srcDstCanvas.setBitmap(srcDst); srcDstCanvas.drawColor(0, PorterDuff.Mode.CLEAR); - mDrawPaint.setColor(color); srcDstCanvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1], mDrawPaint); srcDstCanvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1], mDrawPaint); // draw the bright outline - mDrawPaint.setColor(outlineColor); srcDstCanvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1], mDrawPaint); @@ -148,7 +153,6 @@ public class HolographicOutlineHelper { brightOutline.recycle(); thickOuterBlur.recycle(); thickInnerBlur.recycle(); - glowShape.recycle(); } Bitmap createMediumDropShadow(BubbleTextView view) { diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java index c738480fe..f9424d483 100644 --- a/src/com/android/launcher3/Hotseat.java +++ b/src/com/android/launcher3/Hotseat.java @@ -25,6 +25,7 @@ import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.support.v4.content.ContextCompat; import android.support.v4.graphics.ColorUtils; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -69,7 +70,7 @@ public class Hotseat extends FrameLayout mLauncher = (Launcher) context; mHasVerticalHotseat = mLauncher.getDeviceProfile().isVerticalBarLayout(); mBackgroundColor = ColorUtils.setAlphaComponent( - context.getColor(R.color.all_apps_container_color), 0); + ContextCompat.getColor(context, R.color.all_apps_container_color), 0); mBackground = new ColorDrawable(mBackgroundColor); setBackground(mBackground); } diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index a49162c8b..d3fb38ede 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -853,8 +853,7 @@ public class IconCache { values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(lowResIcon)); values.put(IconDB.COLUMN_LABEL, label); - values.put(IconDB.COLUMN_SYSTEM_STATE, - mIconProvider.getIconSystemState(mIconProvider.getIconSystemState(packageName))); + values.put(IconDB.COLUMN_SYSTEM_STATE, mIconProvider.getIconSystemState(packageName)); return values; } diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java index e136bcd99..398c9d2a2 100644 --- a/src/com/android/launcher3/InfoDropTarget.java +++ b/src/com/android/launcher3/InfoDropTarget.java @@ -100,6 +100,7 @@ public class InfoDropTarget extends UninstallDropTarget { Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1; return developmentSettingsEnabled && (info instanceof AppInfo || info instanceof ShortcutInfo - || info instanceof PendingAddItemInfo || info instanceof LauncherAppWidgetInfo); + || info instanceof PendingAddItemInfo || info instanceof LauncherAppWidgetInfo) + && info.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; } } diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index df87cc204..d8e58d829 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -33,6 +33,7 @@ import com.android.launcher3.compat.LauncherActivityInfoCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Thunk; import org.json.JSONException; @@ -146,6 +147,15 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } PendingInstallShortcutInfo info = createPendingInfo(context, data); if (info != null) { + if (!info.isLauncherActivity()) { + // Since its a custom shortcut, verify that it is safe to launch. + if (!PackageManagerHelper.hasPermissionForActivity( + context, info.launchIntent, null)) { + // Target cannot be launched, or requires some special permission to launch + Log.e(TAG, "Ignoring malicious intent " + info.launchIntent.toUri(0)); + return; + } + } queuePendingShortcutInfo(info, context); } } diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java index 2a94e55c0..c0c22a325 100644 --- a/src/com/android/launcher3/ItemInfo.java +++ b/src/com/android/launcher3/ItemInfo.java @@ -189,10 +189,24 @@ public class ItemInfo { } @Override - public String toString() { - return "Item(id=" + this.id + " type=" + this.itemType + " container=" + this.container - + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX - + " spanY=" + spanY + " user=" + user + ")"; + public final String toString() { + return getClass().getSimpleName() + "(" + dumpProperties() + ")"; + } + + protected String dumpProperties() { + return "id=" + id + + " type=" + itemType + + " container=" + container + + " screen=" + screenId + + " cellX=" + cellX + + " cellY=" + cellY + + " spanX=" + spanX + + " spanY=" + spanY + + " minSpanX=" + minSpanX + + " minSpanY=" + minSpanY + + " rank=" + rank + + " user=" + user + + " title=" + title; } /** diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 1caec339b..b6474e6ef 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -25,7 +25,6 @@ import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; -import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.AlertDialog; import android.app.SearchManager; @@ -36,7 +35,6 @@ import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks2; import android.content.ComponentName; -import android.content.ContentValues; import android.content.Context; import android.content.ContextWrapper; import android.content.DialogInterface; @@ -51,10 +49,8 @@ import android.content.res.Configuration; import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Build; @@ -81,7 +77,6 @@ import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.animation.OvershootInterpolator; @@ -106,6 +101,7 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dynamicui.ExtractedColors; import com.android.launcher3.folder.Folder; @@ -119,10 +115,12 @@ import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.DeepShortcutsContainer; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.userevent.nano.LauncherLogProto; +import com.android.launcher3.util.ActivityResultInfo; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageManagerHelper; +import com.android.launcher3.util.PendingRequestArgs; import com.android.launcher3.util.TestingUtils; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.ViewOnDrawExecutor; @@ -164,10 +162,6 @@ public class Launcher extends Activity private static final int REQUEST_PERMISSION_CALL_PHONE = 13; - private static final int WORKSPACE_BACKGROUND_GRADIENT = 0; - private static final int WORKSPACE_BACKGROUND_TRANSPARENT = 1; - private static final int WORKSPACE_BACKGROUND_BLACK = 2; - private static final float BOUNCE_ANIMATION_TENSION = 1.3f; /** @@ -191,15 +185,11 @@ public class Launcher extends Activity private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen"; // Type: int private static final String RUNTIME_STATE = "launcher.state"; - // Type: Content Values / parcelable - private static final String RUNTIME_STATE_PENDING_ADD_ITEM = "launcher.add_item"; - // Type: parcelable - private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info"; - // Type: parcelable - private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id"; - - static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed"; - static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed"; + // Type: PendingRequestArgs + private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args"; + // Type: ActivityResultInfo + private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result"; + static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown"; /** The different states that Launcher can be in. */ @@ -246,10 +236,6 @@ public class Launcher extends Activity private AppWidgetManagerCompat mAppWidgetManager; private LauncherAppWidgetHost mAppWidgetHost; - @Thunk final ItemInfo mPendingAddInfo = new ItemInfo(); - private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo; - private int mPendingAddWidgetId = -1; - private int[] mTmpAddItemCellCoordinates = new int[2]; @Thunk Hotseat mHotseat; @@ -279,8 +265,6 @@ public class Launcher extends Activity @Thunk boolean mWorkspaceLoading = true; private boolean mPaused = true; - private boolean mRestoring; - private boolean mWaitingForResult; private boolean mOnResumeNeedsLoad; private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>(); @@ -316,10 +300,7 @@ public class Launcher extends Activity // match the sensor state. private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500; - @Thunk Drawable mWorkspaceBackgroundDrawable; - private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>(); - private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false; // We only want to get the SharedPreferences once since it does an FS stat each time we get // it from the context. @@ -363,17 +344,13 @@ public class Launcher extends Activity } }; - private static PendingAddArguments sPendingAddItem; - - @Thunk static class PendingAddArguments { - int requestCode; - Intent intent; - long container; - long screenId; - int cellX; - int cellY; - int appWidgetId; - } + // Activity result which needs to be processed after workspace has loaded. + private ActivityResultInfo mPendingActivityResult; + /** + * Holds extra information required to handle a result from an external call, like + * {@link #startActivityForResult(Intent, int)} or {@link #requestPermissions(String[], int)} + */ + private PendingRequestArgs mPendingRequestArgs; private UserEventDispatcher mUserEventDispatcher; @@ -464,20 +441,14 @@ public class Launcher extends Activity Trace.endSection(); } - if (!mRestoring) { - if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) { - // If the user leaves launcher, then we should just load items asynchronously when - // they return. - mModel.startLoader(PagedView.INVALID_RESTORE_PAGE); - } else { - // We only load the page synchronously if the user rotates (or triggers a - // configuration change) while launcher is in the foreground - if (!mModel.startLoader(mWorkspace.getRestorePage())) { - // If we are not binding synchronously, show a fade in animation when - // the first page bind completes. - mDragLayer.setAlpha(0); - } - } + // We only load the page synchronously if the user rotates (or triggers a + // configuration change) while launcher is in the foreground + if (!mModel.startLoader(mWorkspace.getRestorePage())) { + // If we are not binding synchronously, show a fade in animation when + // the first page bind completes. + mDragLayer.setAlpha(0); + } else { + setWorkspaceLoading(true); } // For handling default keys @@ -504,12 +475,6 @@ public class Launcher extends Activity if (mLauncherCallbacks != null) { mLauncherCallbacks.onCreate(savedInstanceState); } - - if (shouldShowIntroScreen()) { - showIntroScreen(); - } else { - showFirstRunActivity(); - } } @Override @@ -676,53 +641,61 @@ public class Launcher extends Activity * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have * a configuration step, this allows the proper animations to run after other transitions. */ - private long completeAdd(PendingAddArguments args) { - long screenId = args.screenId; - if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + private long completeAdd( + int requestCode, Intent intent, int appWidgetId, PendingRequestArgs info) { + long screenId = info.screenId; + if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { // When the screen id represents an actual screen (as opposed to a rank) we make sure // that the drop page actually exists. - screenId = ensurePendingDropLayoutExists(args.screenId); + screenId = ensurePendingDropLayoutExists(info.screenId); } - switch (args.requestCode) { + switch (requestCode) { case REQUEST_CREATE_SHORTCUT: - completeAddShortcut(args.intent, args.container, screenId, args.cellX, - args.cellY); + completeAddShortcut(intent, info.container, screenId, info.cellX, info.cellY, info); break; case REQUEST_CREATE_APPWIDGET: - completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null); + completeAddAppWidget(appWidgetId, info, null, null); break; case REQUEST_RECONFIGURE_APPWIDGET: - completeRestoreAppWidget(args.appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED); + completeRestoreAppWidget(appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED); break; case REQUEST_BIND_PENDING_APPWIDGET: { - int widgetId = args.appWidgetId; - LauncherAppWidgetInfo info = + int widgetId = appWidgetId; + LauncherAppWidgetInfo widgetInfo = completeRestoreAppWidget(widgetId, LauncherAppWidgetInfo.FLAG_UI_NOT_READY); - if (info != null) { + if (widgetInfo != null) { // Since the view was just bound, also launch the configure activity if needed LauncherAppWidgetProviderInfo provider = mAppWidgetManager .getLauncherAppWidgetInfo(widgetId); if (provider != null && provider.configure != null) { - startRestoredWidgetReconfigActivity(provider, info); + startRestoredWidgetReconfigActivity(provider, widgetInfo); } } break; } } - // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen, - // if you turned the screen off and then back while in All Apps, Launcher would not - // return to the workspace. Clearing mAddInfo.container here fixes this issue - resetAddInfo(); + return screenId; } private void handleActivityResult( final int requestCode, final int resultCode, final Intent data) { + if (isWorkspaceLoading()) { + // process the result once the workspace has loaded. + mPendingActivityResult = new ActivityResultInfo(requestCode, resultCode, data); + return; + } + mPendingActivityResult = null; + // Reset the startActivity waiting flag - setWaitingForResult(false); - final int pendingAddWidgetId = mPendingAddWidgetId; - mPendingAddWidgetId = -1; + final PendingRequestArgs requestArgs = mPendingRequestArgs; + setWaitingForResult(null); + if (requestArgs == null) { + return; + } + + final int pendingAddWidgetId = requestArgs.getWidgetId(); Runnable exitSpringLoaded = new Runnable() { @Override @@ -737,12 +710,14 @@ public class Launcher extends Activity final int appWidgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; if (resultCode == RESULT_CANCELED) { - completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId); + completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs); mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); } else if (resultCode == RESULT_OK) { - addAppWidgetImpl(appWidgetId, mPendingAddInfo, null, - mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY); + addAppWidgetImpl( + appWidgetId, requestArgs, null, + requestArgs.getWidgetProvider(), + ON_ACTIVITY_RESULT_ANIMATION_DELAY); } return; } else if (requestCode == REQUEST_PICK_WALLPAPER) { @@ -758,7 +733,6 @@ public class Launcher extends Activity boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET || requestCode == REQUEST_CREATE_APPWIDGET); - final boolean workspaceLocked = isWorkspaceLocked(); // We have special handling for widgets if (isWidgetDrop) { final int appWidgetId; @@ -775,46 +749,36 @@ public class Launcher extends Activity Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " + "returned from the widget configuration activity."); result = RESULT_CANCELED; - completeTwoStageWidgetDrop(result, appWidgetId); + completeTwoStageWidgetDrop(result, appWidgetId, requestArgs); final Runnable onComplete = new Runnable() { @Override public void run() { exitSpringLoadedDragModeDelayed(false, 0, null); } }; - if (workspaceLocked) { - // No need to remove the empty screen if we're mid-binding, as the - // the bind will not add the empty screen. - mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY); - } else { - mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, - ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); - } - } else { - if (!workspaceLocked) { - if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { - // When the screen id represents an actual screen (as opposed to a rank) - // we make sure that the drop page actually exists. - mPendingAddInfo.screenId = - ensurePendingDropLayoutExists(mPendingAddInfo.screenId); - } - final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId); - dropLayout.setDropPending(true); - final Runnable onComplete = new Runnable() { - @Override - public void run() { - completeTwoStageWidgetDrop(resultCode, appWidgetId); - dropLayout.setDropPending(false); - } - }; - mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, - ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); - } else { - PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId, - mPendingAddInfo); - sPendingAddItem = args; + mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, + ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); + } else { + if (requestArgs.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + // When the screen id represents an actual screen (as opposed to a rank) + // we make sure that the drop page actually exists. + requestArgs.screenId = + ensurePendingDropLayoutExists(requestArgs.screenId); } + final CellLayout dropLayout = + mWorkspace.getScreenWithId(requestArgs.screenId); + + dropLayout.setDropPending(true); + final Runnable onComplete = new Runnable() { + @Override + public void run() { + completeTwoStageWidgetDrop(resultCode, appWidgetId, requestArgs); + dropLayout.setDropPending(false); + } + }; + mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, + ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); } return; } @@ -823,13 +787,7 @@ public class Launcher extends Activity || requestCode == REQUEST_BIND_PENDING_APPWIDGET) { if (resultCode == RESULT_OK) { // Update the widget view. - PendingAddArguments args = preparePendingAddArgs(requestCode, data, - pendingAddWidgetId, mPendingAddInfo); - if (workspaceLocked) { - sPendingAddItem = args; - } else { - completeAdd(args); - } + completeAdd(requestCode, data, pendingAddWidgetId, requestArgs); } // Leave the widget in the pending state if the user canceled the configure. return; @@ -837,23 +795,17 @@ public class Launcher extends Activity if (requestCode == REQUEST_CREATE_SHORTCUT) { // Handle custom shortcuts created using ACTION_CREATE_SHORTCUT. - if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) { - final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1, - mPendingAddInfo); - if (isWorkspaceLocked()) { - sPendingAddItem = args; - } else { - completeAdd(args); - mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, - ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); - } + if (resultCode == RESULT_OK && requestArgs.container != ItemInfo.NO_ID) { + completeAdd(requestCode, data, -1, requestArgs); + mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, + ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); + } else if (resultCode == RESULT_CANCELED) { mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); } } mDragLayer.clearAnimatedView(); - } @Override @@ -868,22 +820,25 @@ public class Launcher extends Activity /** @Override for MNC */ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - if (requestCode == REQUEST_PERMISSION_CALL_PHONE && sPendingAddItem != null - && sPendingAddItem.requestCode == REQUEST_PERMISSION_CALL_PHONE) { + PendingRequestArgs pendingArgs = mPendingRequestArgs; + if (requestCode == REQUEST_PERMISSION_CALL_PHONE && pendingArgs != null + && pendingArgs.getRequestCode() == REQUEST_PERMISSION_CALL_PHONE) { + setWaitingForResult(null); + View v = null; - CellLayout layout = getCellLayout(sPendingAddItem.container, sPendingAddItem.screenId); + CellLayout layout = getCellLayout(pendingArgs.container, pendingArgs.screenId); if (layout != null) { - v = layout.getChildAt(sPendingAddItem.cellX, sPendingAddItem.cellY); + v = layout.getChildAt(pendingArgs.cellX, pendingArgs.cellY); } - Intent intent = sPendingAddItem.intent; - sPendingAddItem = null; + Intent intent = pendingArgs.getPendingIntent(); + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { startActivitySafely(v, intent, null); } else { // TODO: Show a snack bar with link to settings Toast.makeText(this, getString(R.string.msg_no_phone_permission, - getString(R.string.app_name)), Toast.LENGTH_SHORT).show(); + getString(R.string.derived_app_name)), Toast.LENGTH_SHORT).show(); } } if (mLauncherCallbacks != null) { @@ -892,19 +847,6 @@ public class Launcher extends Activity } } - private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int - appWidgetId, ItemInfo info) { - PendingAddArguments args = new PendingAddArguments(); - args.requestCode = requestCode; - args.intent = data; - args.container = info.container; - args.screenId = info.screenId; - args.cellX = info.cellX; - args.cellY = info.cellY; - args.appWidgetId = appWidgetId; - return args; - } - /** * Check to see if a given screen id exists. If not, create it at the end, return the new id. * @@ -923,8 +865,9 @@ public class Launcher extends Activity } } - @Thunk void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) { - CellLayout cellLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId); + @Thunk void completeTwoStageWidgetDrop( + final int resultCode, final int appWidgetId, final PendingRequestArgs requestArgs) { + CellLayout cellLayout = mWorkspace.getScreenWithId(requestArgs.screenId); Runnable onCompleteRunnable = null; int animationType = 0; @@ -932,13 +875,12 @@ public class Launcher extends Activity if (resultCode == RESULT_OK) { animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION; final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId, - mPendingAddWidgetInfo); + requestArgs.getWidgetProvider()); boundWidget = layout; onCompleteRunnable = new Runnable() { @Override public void run() { - completeAddAppWidget(appWidgetId, mPendingAddInfo.container, - mPendingAddInfo.screenId, layout, null); + completeAddAppWidget(appWidgetId, requestArgs, layout, null); exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); } @@ -948,7 +890,7 @@ public class Launcher extends Activity animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION; } if (mDragLayer.getAnimatedView() != null) { - mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout, + mWorkspace.animateWidgetDrop(requestArgs, cellLayout, (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable, animationType, boundWidget, true); } else if (onCompleteRunnable != null) { @@ -1009,21 +951,16 @@ public class Launcher extends Activity // view after launching an app, as they may be depending on the UI to be static to // switch to another app, otherwise, if it was showAppsView(false /* animated */, !launchedFromApp /* updatePredictedApps */, - false /* focusSearchBar */); + mAppsView.shouldRestoreImeState() /* focusSearchBar */); } else if (mOnResumeState == State.WIDGETS) { showWidgetsView(false, false); } mOnResumeState = State.NONE; - // Background was set to gradient in onPause(), restore to transparent if in all apps. - setWorkspaceBackground(mState == State.WORKSPACE ? WORKSPACE_BACKGROUND_GRADIENT - : WORKSPACE_BACKGROUND_TRANSPARENT); - mPaused = false; - if (mRestoring || mOnResumeNeedsLoad) { + if (mOnResumeNeedsLoad) { setWorkspaceLoading(true); mModel.startLoader(getCurrentWorkspaceScreen()); - mRestoring = false; mOnResumeNeedsLoad = false; } if (mBindOnResumeCallbacks.size() > 0) { @@ -1295,7 +1232,8 @@ public class Launcher extends Activity return mDefaultKeySsb.toString(); } - private void clearTypedText() { + @Override + public void clearTypedText() { mDefaultKeySsb.clear(); mDefaultKeySsb.clearSpans(); Selection.setSelection(mDefaultKeySsb, 0); @@ -1338,18 +1276,12 @@ public class Launcher extends Activity mWorkspace.setRestorePage(currentScreen); } - ContentValues itemValues = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_ITEM); - if (itemValues != null) { - mPendingAddInfo.readFromValues(itemValues); - AppWidgetProviderInfo info = savedState.getParcelable( - RUNTIME_STATE_PENDING_ADD_WIDGET_INFO); - mPendingAddWidgetInfo = info == null ? - null : LauncherAppWidgetProviderInfo.fromProviderInfo(this, info); - - mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID); - setWaitingForResult(true); - mRestoring = true; + PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS); + if (requestArgs != null) { + setWaitingForResult(requestArgs); } + + mPendingActivityResult = savedState.getParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT); } /** @@ -1367,10 +1299,8 @@ public class Launcher extends Activity mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg); // Setup the drag layer - mDragLayer.setup(this, mDragController, mAllAppsController); // Setup the hotseat @@ -1527,12 +1457,19 @@ public class Launcher extends Activity * @param data The intent describing the shortcut. */ private void completeAddShortcut(Intent data, long container, long screenId, int cellX, - int cellY) { + int cellY, PendingRequestArgs args) { int[] cellXY = mTmpAddItemCellCoordinates; CellLayout layout = getCellLayout(container, screenId); ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(this, data); - if (info == null) { + if (info == null || args.getRequestCode() != REQUEST_CREATE_SHORTCUT || + args.getPendingIntent().getComponent() == null) { + return; + } + if (!PackageManagerHelper.hasPermissionForActivity( + this, info.intent, args.getPendingIntent().getComponent().getPackageName())) { + // The app is trying to add a shortcut without sufficient permissions + Log.e(TAG, "Ignoring malicious intent " + info.intent.toUri(0)); return; } final View view = createShortcut(info); @@ -1566,10 +1503,8 @@ public class Launcher extends Activity LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1]); - if (!mRestoring) { - mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, - isWorkspaceLocked()); - } + mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, + isWorkspaceLocked()); } /** @@ -1577,10 +1512,9 @@ public class Launcher extends Activity * * @param appWidgetId The app widget id */ - @Thunk void completeAddAppWidget(int appWidgetId, long container, long screenId, + @Thunk void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo, AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) { - ItemInfo info = mPendingAddInfo; if (appWidgetInfo == null) { appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId); } @@ -1591,24 +1525,21 @@ public class Launcher extends Activity LauncherAppWidgetInfo launcherInfo; launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider); - launcherInfo.spanX = info.spanX; - launcherInfo.spanY = info.spanY; - launcherInfo.minSpanX = info.minSpanX; - launcherInfo.minSpanY = info.minSpanY; + launcherInfo.spanX = itemInfo.spanX; + launcherInfo.spanY = itemInfo.spanY; + launcherInfo.minSpanX = itemInfo.minSpanX; + launcherInfo.minSpanY = itemInfo.minSpanY; launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo); LauncherModel.addItemToDatabase(this, launcherInfo, - container, screenId, info.cellX, info.cellY); + itemInfo.container, itemInfo.screenId, itemInfo.cellX, itemInfo.cellY); - if (!mRestoring) { - if (hostView == null) { - // Perform actual inflation because we're live - hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); - } - hostView.setVisibility(View.VISIBLE); - addAppWidgetToWorkspace(hostView, launcherInfo, appWidgetInfo, isWorkspaceLocked()); + if (hostView == null) { + // Perform actual inflation because we're live + hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); } - resetAddInfo(); + hostView.setVisibility(View.VISIBLE); + addAppWidgetToWorkspace(hostView, launcherInfo, appWidgetInfo, isWorkspaceLocked()); } private void addAppWidgetToWorkspace( @@ -1639,8 +1570,7 @@ public class Launcher extends Activity // Reset AllApps to its initial state only if we are not in the middle of // processing a multi-step drop - if (mAppsView != null && mWidgetsView != null && - mPendingAddInfo.container == ItemInfo.NO_ID) { + if (mAppsView != null && mWidgetsView != null && mPendingRequestArgs == null) { if (!showWorkspace(false)) { // If we are already on the workspace, then manually reset all apps mAppsView.reset(); @@ -1852,7 +1782,7 @@ public class Launcher extends Activity getWindow().closeAllPanels(); // Whatever we were doing is hereby canceled. - setWaitingForResult(false); + setWaitingForResult(null); } @Override @@ -1970,13 +1900,11 @@ public class Launcher extends Activity closeFolder(false); closeShortcutsContainer(false); - if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 && - mWaitingForResult) { - ContentValues itemValues = new ContentValues(); - mPendingAddInfo.writeToValues(itemValues); - outState.putParcelable(RUNTIME_STATE_PENDING_ADD_ITEM, itemValues); - outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo); - outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId); + if (mPendingRequestArgs != null) { + outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs); + } + if (mPendingActivityResult != null) { + outState.putParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT, mPendingActivityResult); } if (mLauncherCallbacks != null) { @@ -2039,14 +1967,12 @@ public class Launcher extends Activity @Override public void startActivityForResult(Intent intent, int requestCode, Bundle options) { - onStartForResult(requestCode); super.startActivityForResult(intent, requestCode, options); } @Override public void startIntentSenderForResult (IntentSender intent, int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) { - onStartForResult(requestCode); try { super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, flagsValues, extraFlags, options); @@ -2055,12 +1981,6 @@ public class Launcher extends Activity } } - private void onStartForResult(int requestCode) { - if (requestCode >= 0) { - setWaitingForResult(true); - } - } - /** * Indicates that we want global search for this activity by setting the globalSearch * argument for {@link #startSearch} to true. @@ -2078,13 +1998,10 @@ public class Launcher extends Activity appSearchData.putString("source", "launcher-search"); } - // TODO send proper bounds. - Rect sourceBounds = null; - - boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery, - appSearchData, sourceBounds); - if (clearTextImmediately) { - clearTypedText(); + if (mLauncherCallbacks == null || + !mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData)) { + // Starting search from the callbacks failed. Start the default global search. + startGlobalSearch(initialQuery, selectInitialQuery, appSearchData, null); } // We need to show the workspace after starting the search @@ -2092,28 +2009,9 @@ public class Launcher extends Activity } /** - * Start a text search. - * - * @return {@code true} if the search will start immediately, so any further keypresses - * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue - * to buffer keypresses. - */ - public boolean startSearch(String initialQuery, - boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) { - if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) { - return mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData, - sourceBounds); - } - - startGlobalSearch(initialQuery, selectInitialQuery, - appSearchData, sourceBounds); - return false; - } - - /** * Starts the global search activity. This code is a copied from SearchManager */ - private void startGlobalSearch(String initialQuery, + public void startGlobalSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) { final SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); @@ -2171,7 +2069,7 @@ public class Launcher extends Activity } public boolean isWorkspaceLocked() { - return mWorkspaceLoading || mWaitingForResult; + return mWorkspaceLoading || mPendingRequestArgs != null; } public boolean isWorkspaceLoading() { @@ -2186,9 +2084,9 @@ public class Launcher extends Activity } } - private void setWaitingForResult(boolean value) { + private void setWaitingForResult(PendingRequestArgs args) { boolean isLocked = isWorkspaceLocked(); - mWaitingForResult = value; + mPendingRequestArgs = args; if (isLocked != isWorkspaceLocked()) { onWorkspaceLockedChanged(); } @@ -2200,33 +2098,23 @@ public class Launcher extends Activity } } - private void resetAddInfo() { - mPendingAddInfo.container = ItemInfo.NO_ID; - mPendingAddInfo.screenId = -1; - mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1; - mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1; - mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = 1; - } - - void addAppWidgetFromDropImpl(final int appWidgetId, final ItemInfo info, final - AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo) { + void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget, + LauncherAppWidgetProviderInfo appWidgetInfo) { if (LOGD) { Log.d(TAG, "Adding widget from drop"); } addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0); } - void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, - final AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo, + void addAppWidgetImpl(int appWidgetId, ItemInfo info, + AppWidgetHostView boundWidget, LauncherAppWidgetProviderInfo appWidgetInfo, int delay) { if (appWidgetInfo.configure != null) { - mPendingAddWidgetInfo = appWidgetInfo; - mPendingAddWidgetId = appWidgetId; + setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, appWidgetInfo, info)); // Launch over to configure widget, if needed mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this, mAppWidgetHost, REQUEST_CREATE_APPWIDGET); - } else { // Otherwise just add it Runnable onComplete = new Runnable() { @@ -2237,8 +2125,7 @@ public class Launcher extends Activity null); } }; - completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget, - appWidgetInfo); + completeAddAppWidget(appWidgetId, info, boundWidget, appWidgetInfo); mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false); } } @@ -2251,17 +2138,22 @@ public class Launcher extends Activity public void addPendingItem(PendingAddItemInfo info, long container, long screenId, int[] cell, int spanX, int spanY) { + info.container = container; + info.screenId = screenId; + if (cell != null) { + info.cellX = cell[0]; + info.cellY = cell[1]; + } + info.spanX = spanX; + info.spanY = spanY; + switch (info.itemType) { case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - int span[] = new int[2]; - span[0] = spanX; - span[1] = spanY; - addAppWidgetFromDrop((PendingAddWidgetInfo) info, - container, screenId, cell, span); + addAppWidgetFromDrop((PendingAddWidgetInfo) info); break; case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - processShortcutFromDrop(info.componentName, container, screenId, cell); + processShortcutFromDrop(info); break; default: throw new IllegalStateException("Unknown item type: " + info.itemType); @@ -2270,51 +2162,17 @@ public class Launcher extends Activity /** * Process a shortcut drop. - * - * @param componentName The name of the component - * @param screenId The ID of the screen where it should be added - * @param cell The cell it should be added to, optional */ - private void processShortcutFromDrop(ComponentName componentName, long container, long screenId, - int[] cell) { - resetAddInfo(); - mPendingAddInfo.container = container; - mPendingAddInfo.screenId = screenId; - - if (cell != null) { - mPendingAddInfo.cellX = cell[0]; - mPendingAddInfo.cellY = cell[1]; - } - - Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); - createShortcutIntent.setComponent(componentName); - Utilities.startActivityForResultSafely(this, createShortcutIntent, REQUEST_CREATE_SHORTCUT); + private void processShortcutFromDrop(PendingAddItemInfo info) { + Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(info.componentName); + setWaitingForResult(PendingRequestArgs.forIntent(REQUEST_CREATE_SHORTCUT, intent, info)); + Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT); } /** * Process a widget drop. - * - * @param info The PendingAppWidgetInfo of the widget being added. - * @param screenId The ID of the screen where it should be added - * @param cell The cell it should be added to, optional */ - private void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId, - int[] cell, int[] span) { - resetAddInfo(); - mPendingAddInfo.container = info.container = container; - mPendingAddInfo.screenId = info.screenId = screenId; - mPendingAddInfo.minSpanX = info.minSpanX; - mPendingAddInfo.minSpanY = info.minSpanY; - - if (cell != null) { - mPendingAddInfo.cellX = cell[0]; - mPendingAddInfo.cellY = cell[1]; - } - if (span != null) { - mPendingAddInfo.spanX = span[0]; - mPendingAddInfo.spanY = span[1]; - } - + private void addAppWidgetFromDrop(PendingAddWidgetInfo info) { AppWidgetHostView hostView = info.boundWidget; int appWidgetId; if (hostView != null) { @@ -2340,11 +2198,11 @@ public class Launcher extends Activity if (success) { addAppWidgetFromDropImpl(appWidgetId, info, null, info.info); } else { - mPendingAddWidgetInfo = info.info; + setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, info.info, info)); Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName); - mAppWidgetManager.getUser(mPendingAddWidgetInfo) + mAppWidgetManager.getUser(info.info) .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE); // TODO: we need to make sure that this accounts for the options bundle. // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options); @@ -2563,14 +2421,13 @@ public class Launcher extends Activity LauncherAppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.findProvider(info.providerName, info.user); if (appWidgetInfo != null) { - mPendingAddWidgetId = info.appWidgetId; - mPendingAddInfo.copyFrom(info); - mPendingAddWidgetInfo = appWidgetInfo; + setWaitingForResult(PendingRequestArgs + .forWidgetInfo(info.appWidgetId, appWidgetInfo, info)); Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mPendingAddWidgetId); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, info.appWidgetId); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, appWidgetInfo.provider); - mAppWidgetManager.getUser(mPendingAddWidgetInfo) + mAppWidgetManager.getUser(appWidgetInfo) .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE); startActivityForResult(intent, REQUEST_BIND_PENDING_APPWIDGET); } @@ -2599,9 +2456,7 @@ public class Launcher extends Activity private void startRestoredWidgetReconfigActivity( LauncherAppWidgetProviderInfo provider, LauncherAppWidgetInfo info) { - mPendingAddWidgetInfo = provider; - mPendingAddInfo.copyFrom(info); - mPendingAddWidgetId = info.appWidgetId; + setWaitingForResult(PendingRequestArgs.forWidgetInfo(info.appWidgetId, provider, info)); mAppWidgetManager.startConfigActivity(provider, info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET); } @@ -2771,6 +2626,7 @@ public class Launcher extends Activity int pageScroll = mWorkspace.getScrollForPage(mWorkspace.getPageNearestToCenterOfScreen()); float offset = mWorkspace.mWallpaperOffset.wallpaperOffsetForScroll(pageScroll); + setWaitingForResult(new PendingRequestArgs(new ItemInfo())); Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER) .setPackage(pickerPackage) .putExtra(Utilities.EXTRA_WALLPAPER_OFFSET, offset); @@ -2784,8 +2640,10 @@ public class Launcher extends Activity */ public void onClickSettingsButton(View v) { if (LOGD) Log.d(TAG, "onClickSettingsButton"); - startActivity(new Intent(Utilities.ACTION_APPLICATION_PREFERENCES) - .setPackage(getPackageName())); + Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES) + .setPackage(getPackageName()); + intent.setSourceBounds(getViewBounds(v)); + startActivity(intent, getActivityLaunchOptions(v)); } public View.OnTouchListener getHapticFeedbackTouchListener() { @@ -2809,7 +2667,7 @@ public class Launcher extends Activity mDragLayer.onAccessibilityStateChanged(enabled); } - public void onDragStarted(View view) { + public void onDragStarted() { if (isOnCustomContent()) { // Custom content screen doesn't participate in drag and drop. If on custom // content screen, move to default. @@ -2885,9 +2743,9 @@ public class Launcher extends Activity && Intent.ACTION_CALL.equals(intent.getAction()) && checkSelfPermission(Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { - // TODO: Rename sPendingAddItem to a generic name. - sPendingAddItem = preparePendingAddArgs(REQUEST_PERMISSION_CALL_PHONE, intent, - 0, info); + + setWaitingForResult(PendingRequestArgs + .forIntent(REQUEST_PERMISSION_CALL_PHONE, intent, info)); requestPermissions(new String[]{Manifest.permission.CALL_PHONE}, REQUEST_PERMISSION_CALL_PHONE); } else { @@ -3172,6 +3030,14 @@ public class Launcher extends Activity } } + public View getTopFloatingView() { + View topView = getOpenShortcutsContainer(); + if (topView == null) { + topView = getWorkspace().getOpenFolder(); + } + return topView; + } + /** * @return The open shortcuts container, or null if there is none */ @@ -3221,7 +3087,7 @@ public class Launcher extends Activity ItemInfo info = (ItemInfo) v.getTag(); longClickCellInfo = new CellLayout.CellInfo(v, info); itemUnderLongClick = longClickCellInfo.cell; - resetAddInfo(); + mPendingRequestArgs = null; } // The hotseat touch handling does not go through Workspace, and we always allow long press @@ -3243,7 +3109,7 @@ public class Launcher extends Activity longClickCellInfo.cellX, longClickCellInfo.cellY)); if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) { // User long pressed on an item - mWorkspace.startDrag(longClickCellInfo); + mWorkspace.startDrag(longClickCellInfo, new DragOptions()); } } } @@ -3285,29 +3151,6 @@ public class Launcher extends Activity return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS); } - private void setWorkspaceBackground(int background) { - switch (background) { - case WORKSPACE_BACKGROUND_TRANSPARENT: - getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - break; - case WORKSPACE_BACKGROUND_BLACK: - getWindow().setBackgroundDrawable(null); - break; - default: - getWindow().setBackgroundDrawable(mWorkspaceBackgroundDrawable); - } - } - - protected void changeWallpaperVisiblity(boolean visible) { - int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0; - int curflags = getWindow().getAttributes().flags - & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; - if (wpflags != curflags) { - getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER); - } - setWorkspaceBackground(visible ? WORKSPACE_BACKGROUND_GRADIENT : WORKSPACE_BACKGROUND_BLACK); - } - @Override public void onTrimMemory(int level) { super.onTrimMemory(level); @@ -3564,10 +3407,6 @@ public class Launcher extends Activity // TODO } - public boolean launcherCallbacksProvidesSearch() { - return (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()); - } - @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { final boolean result = super.dispatchPopulateAccessibilityEvent(event); @@ -3955,14 +3794,30 @@ public class Launcher extends Activity pendingInfo.minSpanX = item.minSpanX; pendingInfo.minSpanY = item.minSpanY; Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo); + + boolean isDirectConfig = + item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG); + if (isDirectConfig && item.bindOptions != null) { + Bundle newOptions = item.bindOptions.getExtras(); + if (options != null) { + newOptions.putAll(options); + } + options = newOptions; + } boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( item.appWidgetId, appWidgetInfo, options); + // We tried to bind once. If we were not able to bind, we would need to + // go through the permission dialog, which means we cannot skip the config + // activity. + item.bindOptions = null; + item.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG; + // Bind succeeded if (success) { // If the widget has a configure activity, it is still needs to set it up, // otherwise the widget is ready to go. - item.restoreStatus = (appWidgetInfo.configure == null) + item.restoreStatus = (appWidgetInfo.configure == null) || isDirectConfig ? LauncherAppWidgetInfo.RESTORE_COMPLETED : LauncherAppWidgetInfo.FLAG_UI_NOT_READY; } @@ -4105,21 +3960,10 @@ public class Launcher extends Activity setWorkspaceLoading(false); - // If we received the result of any pending adds while the loader was running (e.g. the - // widget configuration forced an orientation change), process them now. - if (sPendingAddItem != null) { - final long screenId = completeAdd(sPendingAddItem); - - // TODO: this moves the user to the page where the pending item was added. Ideally, - // the screen would be guaranteed to exist after bind, and the page would be set through - // the workspace restore process. - mWorkspace.post(new Runnable() { - @Override - public void run() { - mWorkspace.snapToScreenId(screenId); - } - }); - sPendingAddItem = null; + if (mPendingActivityResult != null) { + handleActivityResult(mPendingActivityResult.requestCode, + mPendingActivityResult.resultCode, mPendingActivityResult.data); + mPendingActivityResult = null; } InstallShortcutReceiver.disableAndFlushInstallQueue(this); @@ -4202,6 +4046,10 @@ public class Launcher extends Activity return Collections.EMPTY_LIST; } ComponentName component = info.getTargetComponent(); + if (component == null) { + return Collections.EMPTY_LIST; + } + List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user)); return ids == null ? Collections.EMPTY_LIST : ids; } @@ -4442,51 +4290,6 @@ public class Launcher extends Activity } } - /** - * To be overridden by subclasses to indicate that there is an activity to launch - * before showing the standard launcher experience. - */ - protected boolean hasFirstRunActivity() { - if (mLauncherCallbacks != null) { - return mLauncherCallbacks.hasFirstRunActivity(); - } - return false; - } - - /** - * To be overridden by subclasses to launch any first run activity - */ - protected Intent getFirstRunActivity() { - if (mLauncherCallbacks != null) { - return mLauncherCallbacks.getFirstRunActivity(); - } - return null; - } - - private boolean shouldRunFirstRunActivity() { - return !ActivityManager.isRunningInTestHarness() && - !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false); - } - - public boolean showFirstRunActivity() { - if (shouldRunFirstRunActivity() && - hasFirstRunActivity()) { - Intent firstRunIntent = getFirstRunActivity(); - if (firstRunIntent != null) { - startActivity(firstRunIntent); - markFirstRunActivityShown(); - return true; - } - } - return false; - } - - private void markFirstRunActivityShown() { - SharedPreferences.Editor editor = mSharedPrefs.edit(); - editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true); - editor.apply(); - } - private void markAppsViewShown() { if (mSharedPrefs.getBoolean(APPS_VIEW_SHOWN, false)) { return; @@ -4510,44 +4313,6 @@ public class Launcher extends Activity return true; } - /** - * To be overridden by subclasses to indicate that there is an in-activity full-screen intro - * screen that must be displayed and dismissed. - */ - protected boolean hasDismissableIntroScreen() { - if (mLauncherCallbacks != null) { - return mLauncherCallbacks.hasDismissableIntroScreen(); - } - return false; - } - - /** - * Full screen intro screen to be shown and dismissed before the launcher can be used. - */ - protected View getIntroScreen() { - if (mLauncherCallbacks != null) { - return mLauncherCallbacks.getIntroScreen(); - } - return null; - } - - /** - * To be overriden by subclasses to indicate whether the in-activity intro screen has been - * dismissed. This method is ignored if #hasDismissableIntroScreen returns false. - */ - private boolean shouldShowIntroScreen() { - return hasDismissableIntroScreen() && - !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false); - } - - protected void showIntroScreen() { - View introScreen = getIntroScreen(); - changeWallpaperVisiblity(false); - if (introScreen != null) { - mDragLayer.showOverlayView(introScreen); - } - } - // TODO: These method should be a part of LauncherSearchCallback @TargetApi(Build.VERSION_CODES.LOLLIPOP) public ItemInfo createAppDragInfo(Intent appLaunchIntent) { @@ -4614,8 +4379,8 @@ public class Launcher extends Activity Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this); Log.d(TAG, "mSavedState=" + mSavedState); Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading); - Log.d(TAG, "mRestoring=" + mRestoring); - Log.d(TAG, "mWaitingForResult=" + mWaitingForResult); + Log.d(TAG, "mPendingRequestArgs=" + mPendingRequestArgs); + Log.d(TAG, "mPendingActivityResult=" + mPendingActivityResult); mModel.dumpState(); // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState(); diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java index 99210fd34..66d895726 100644 --- a/src/com/android/launcher3/LauncherAppWidgetInfo.java +++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java @@ -20,6 +20,7 @@ import android.appwidget.AppWidgetHostView; import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; +import android.content.Intent; import com.android.launcher3.compat.UserHandleCompat; @@ -57,6 +58,12 @@ public class LauncherAppWidgetInfo extends ItemInfo { public static final int FLAG_ID_ALLOCATED = 16; /** + * Indicates that the widget does not need to show config activity, even if it has a + * configuration screen. It can also optionally have some extras which are sent during bind. + */ + public static final int FLAG_DIRECT_CONFIG = 32; + + /** * Indicates that the widget hasn't been instantiated yet. */ static final int NO_ID = -1; @@ -84,6 +91,11 @@ public class LauncherAppWidgetInfo extends ItemInfo { */ int installProgress = -1; + /** + * Optional extras sent during widget bind. See {@link #FLAG_DIRECT_CONFIG}. + */ + public Intent bindOptions; + private boolean mHasNotifiedInitialWidgetSizeChanged; LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) { @@ -115,6 +127,8 @@ public class LauncherAppWidgetInfo extends ItemInfo { values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString()); values.put(LauncherSettings.Favorites.RESTORED, restoreStatus); + values.put(LauncherSettings.Favorites.INTENT, + bindOptions == null ? null : bindOptions.toUri(0)); } /** @@ -129,8 +143,8 @@ public class LauncherAppWidgetInfo extends ItemInfo { } @Override - public String toString() { - return "AppWidget(id=" + Integer.toString(appWidgetId) + ")"; + protected String dumpProperties() { + return super.dumpProperties() + " appWidgetId=" + appWidgetId; } public final boolean isWidgetIdAllocated() { diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java index 2bbe0fbf3..6394b9052 100644 --- a/src/com/android/launcher3/LauncherCallbacks.java +++ b/src/com/android/launcher3/LauncherCallbacks.java @@ -79,9 +79,11 @@ public interface LauncherCallbacks { @Deprecated public void onWorkspaceLockedChanged(); - public boolean providesSearch(); - public boolean startSearch(String initialQuery, boolean selectInitialQuery, - Bundle appSearchData, Rect sourceBounds); + /** + * Starts a search with {@param initialQuery}. Return false if search was not started. + */ + public boolean startSearch( + String initialQuery, boolean selectInitialQuery, Bundle appSearchData); public boolean hasCustomContentToLeft(); public void populateCustomContentContainer(); public View getQsbBar(); @@ -91,10 +93,6 @@ public interface LauncherCallbacks { * Extensions points for adding / replacing some other aspects of the Launcher experience. */ public UserEventDispatcher getUserEventDispatcher(); - public Intent getFirstRunActivity(); - public boolean hasFirstRunActivity(); - public boolean hasDismissableIntroScreen(); - public View getIntroScreen(); public boolean shouldMoveToDefaultScreenOnHomeIntent(); public boolean hasSettings(); public AllAppsSearchBarController getAllAppsSearchBarController(); diff --git a/src/com/android/launcher3/LauncherExterns.java b/src/com/android/launcher3/LauncherExterns.java index c7a8668de..887859cb0 100644 --- a/src/com/android/launcher3/LauncherExterns.java +++ b/src/com/android/launcher3/LauncherExterns.java @@ -29,4 +29,6 @@ public interface LauncherExterns { public SharedPreferences getSharedPrefs(); public void setLauncherOverlay(Launcher.LauncherOverlay overlay); + + void clearTypedText(); } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index c5c52b4bc..68450e7a7 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -103,7 +103,6 @@ public class LauncherModel extends BroadcastReceiver implements LauncherAppsCompat.OnAppsChangedCallbackCompat { static final boolean DEBUG_LOADERS = false; private static final boolean DEBUG_RECEIVER = false; - private static final boolean REMOVE_UNRESTORED_ICONS = true; static final String TAG = "Launcher.Model"; @@ -1883,12 +1882,12 @@ public class LauncherModel extends BroadcastReceiver restored = false; itemReplaced = true; - } else if (REMOVE_UNRESTORED_ICONS) { + } else { FileLog.d(TAG, "Unrestored package removed: " + cn); itemsToRemove.add(id); continue; } - } else if (REMOVE_UNRESTORED_ICONS) { + } else { FileLog.d(TAG, "Unrestored package removed: " + cn); itemsToRemove.add(id); continue; @@ -2148,7 +2147,7 @@ public class LauncherModel extends BroadcastReceiver // Id would be valid only if the widget restore broadcast was received. if (isIdValid) { - status = LauncherAppWidgetInfo.FLAG_UI_NOT_READY; + status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY; } else { status &= ~LauncherAppWidgetInfo .FLAG_PROVIDER_NOT_READY; @@ -2170,7 +2169,7 @@ public class LauncherModel extends BroadcastReceiver // App restore has started. Update the flag appWidgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; - } else if (REMOVE_UNRESTORED_ICONS && !isSafeMode) { + } else if (!isSafeMode) { FileLog.d(TAG, "Unrestored widget removed: " + component); itemsToRemove.add(id); continue; @@ -2179,6 +2178,14 @@ public class LauncherModel extends BroadcastReceiver appWidgetInfo.installProgress = installProgress == null ? 0 : installProgress; } + if (appWidgetInfo.hasRestoreFlag( + LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) { + intentDescription = c.getString(intentIndex); + if (!TextUtils.isEmpty(intentDescription)) { + appWidgetInfo.bindOptions = + Intent.parseUri(intentDescription, 0); + } + } appWidgetInfo.id = id; appWidgetInfo.screenId = c.getInt(screenIndex); diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index eee562781..f3d949326 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -67,6 +67,7 @@ import com.android.launcher3.util.Thunk; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; +import java.util.Locale; public class LauncherProvider extends ContentProvider { private static final String TAG = "LauncherProvider"; @@ -313,10 +314,32 @@ public class LauncherProvider extends ContentProvider { SqlArguments args = new SqlArguments(uri, selection, selectionArgs); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - int count = db.delete(args.table, args.where, args.args); - if (count > 0) notifyListeners(); - reloadLauncherIfExternal(); + if (Binder.getCallingPid() != Process.myPid() + && Favorites.TABLE_NAME.equalsIgnoreCase(args.table)) { + String widgetSelection = TextUtils.isEmpty(args.where) ? "1=1" : args.where; + widgetSelection = String.format(Locale.ENGLISH, "%1$s = %2$d AND ( %3$s )", + Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET, widgetSelection); + try (Cursor c = db.query(Favorites.TABLE_NAME, new String[] { Favorites.APPWIDGET_ID }, + widgetSelection, args.args, null, null, null)) { + AppWidgetHost host = new AppWidgetHost(getContext(), Launcher.APPWIDGET_HOST_ID); + while (c.moveToNext()) { + int widgetId = c.getInt(0); + if (widgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { + try { + host.deleteAppWidgetId(widgetId); + } catch (RuntimeException e) { + Log.e(TAG, "Error deleting widget id " + widgetId, e); + } + } + } + } + } + int count = db.delete(args.table, args.where, args.args); + if (count > 0) { + notifyListeners(); + reloadLauncherIfExternal(); + } return count; } diff --git a/src/com/android/launcher3/PendingAddItemInfo.java b/src/com/android/launcher3/PendingAddItemInfo.java index 1aaf85bbd..31820d742 100644 --- a/src/com/android/launcher3/PendingAddItemInfo.java +++ b/src/com/android/launcher3/PendingAddItemInfo.java @@ -29,4 +29,9 @@ public class PendingAddItemInfo extends ItemInfo { * The component that will be created. */ public ComponentName componentName; + + @Override + protected String dumpProperties() { + return super.dumpProperties() + " componentName=" + componentName; + } } diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java index 6ee96fc79..48a75d111 100644 --- a/src/com/android/launcher3/PinchToOverviewListener.java +++ b/src/com/android/launcher3/PinchToOverviewListener.java @@ -102,8 +102,8 @@ public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleG // once the state switching animation is complete. return false; } - if (mWorkspace.getOpenFolder() != null) { - // Don't listen for the pinch gesture if a folder is open. + if (mLauncher.getTopFloatingView() != null) { + // Don't listen for the pinch gesture if a floating view is open. return false; } diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index 21fa8a05e..fb9374314 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -158,7 +158,7 @@ public class ShortcutInfo extends ItemInfo { */ Intent promisedIntent; - ShortcutInfo() { + public ShortcutInfo() { itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; } @@ -270,14 +270,6 @@ public class ShortcutInfo extends ItemInfo { } } - @Override - public String toString() { - return "ShortcutInfo(title=" + title + "intent=" + intent + "id=" + this.id - + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId - + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX + " spanY=" + spanY - + " user=" + user + ")"; - } - public ComponentName getTargetComponent() { return promisedIntent != null ? promisedIntent.getComponent() : intent.getComponent(); } diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 045b1d33d..b0e096a2e 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -58,6 +58,8 @@ import android.util.SparseArray; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.widget.Toast; import com.android.launcher3.compat.UserHandleCompat; @@ -168,10 +170,6 @@ public final class Utilities { return false; } - // TODO: Use Intent.ACTION_APPLICATION_PREFERENCES when N SDK is available. - public static final String ACTION_APPLICATION_PREFERENCES - = "android.intent.action.APPLICATION_PREFERENCES"; - public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) { byte[] data = c.getBlob(iconIndex); try { @@ -688,11 +686,11 @@ public final class Utilities { /** * Calculates the height of a given string at a specific text size. */ - public static float calculateTextHeight(float textSizePx) { + public static int calculateTextHeight(float textSizePx) { Paint p = new Paint(); p.setTextSize(textSizePx); Paint.FontMetrics fm = p.getFontMetrics(); - return -fm.top + fm.bottom; + return (int) Math.ceil(fm.bottom - fm.top); } /** @@ -765,16 +763,21 @@ public final class Utilities { } public static boolean isBootCompleted() { + return "1".equals(getSystemProperty("sys.boot_completed", "1")); + } + + public static String getSystemProperty(String property, String defaultValue) { try { Class clazz = Class.forName("android.os.SystemProperties"); Method getter = clazz.getDeclaredMethod("get", String.class); - String value = (String) getter.invoke(null, "sys.boot_completed"); - return "1".equals(value); + String value = (String) getter.invoke(null, property); + if (!TextUtils.isEmpty(value)) { + return value; + } } catch (Exception e) { Log.d(TAG, "Unable to read system properties"); - // Assume that boot has completed - return true; } + return defaultValue; } /** @@ -905,4 +908,15 @@ public final class Utilities { ta.recycle(); return colorAccent; } + + public static void sendCustomAccessibilityEvent(View target, int type, String text) { + AccessibilityManager accessibilityManager = (AccessibilityManager) + target.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + if (accessibilityManager.isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(type); + target.onInitializeAccessibilityEvent(event); + event.getText().add(text); + accessibilityManager.sendAccessibilityEvent(event); + } + } } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 75692a686..c499beeb3 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -59,7 +59,7 @@ import android.widget.TextView; import com.android.launcher3.Launcher.CustomContentCallbacks; import com.android.launcher3.Launcher.LauncherOverlay; import com.android.launcher3.UninstallDropTarget.DropTargetSource; -import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource; +import com.android.launcher3.accessibility.AccessibileDragListenerAdapter; import com.android.launcher3.accessibility.OverviewAccessibilityDelegate; import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate; import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; @@ -69,13 +69,13 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; -import com.android.launcher3.graphics.DragPreviewProvider; +import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragScroller; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dragndrop.SpringLoadedDragController; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.logging.UserEventDispatcher; +import com.android.launcher3.graphics.DragPreviewProvider; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutsContainerListener; import com.android.launcher3.userevent.nano.LauncherLogProto; @@ -84,6 +84,7 @@ import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.MultiStateAlphaController; import com.android.launcher3.util.Thunk; +import com.android.launcher3.util.VerticalFlingDetector; import com.android.launcher3.util.WallpaperOffsetInterpolator; import com.android.launcher3.widget.PendingAddShortcutInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; @@ -100,7 +101,7 @@ import java.util.HashSet; public class Workspace extends PagedView implements DropTarget, DragSource, DragScroller, View.OnTouchListener, DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener, - Insettable, DropTargetSource, AccessibilityDragSource { + Insettable, DropTargetSource { private static final String TAG = "Launcher.Workspace"; private static boolean ENFORCE_DRAG_EVENT_ORDER = false; @@ -134,7 +135,6 @@ public class Workspace extends PagedView @Thunk Runnable mRemoveEmptyScreenRunnable; @Thunk boolean mDeferRemoveExtraEmptyScreen = false; - @Thunk boolean mAddNewPageOnDrag = true; /** * CellInfo for the cell that is currently being dragged @@ -247,8 +247,7 @@ public class Workspace extends PagedView /** Is the user is dragging an item near the edge of a page? */ private boolean mInScrollArea = false; - private HolographicOutlineHelper mOutlineHelper; - @Thunk Bitmap mDragOutline = null; + private DragPreviewProvider mOutlineProvider = null; public static final int DRAG_BITMAP_PADDING = DragPreviewProvider.DRAG_BITMAP_PADDING; private boolean mWorkspaceFadeInAdjacentScreens; @@ -343,8 +342,6 @@ public class Workspace extends PagedView public Workspace(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mOutlineHelper = HolographicOutlineHelper.obtain(context); - mLauncher = (Launcher) context; mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this); final Resources res = getResources(); @@ -412,25 +409,56 @@ public class Workspace extends PagedView } @Override - public void onDragStart(final DragSource source, ItemInfo info, int dragAction) { + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { if (ENFORCE_DRAG_EVENT_ORDER) { enfoceDragParity("onDragStart", 0, 0); } + if (mOutlineProvider != null) { + // The outline is used to visualize where the item will land if dropped + mOutlineProvider.generateDragOutline(mCanvas); + } + updateChildrenLayersEnabled(false); + mLauncher.onDragStarted(); mLauncher.lockScreenOrientation(); mLauncher.onInteractionBegin(); // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging InstallShortcutReceiver.enableInstallQueue(); - if (mAddNewPageOnDrag) { + // Do not add a new page if it is a accessible drag which was not started by the workspace. + // We do not support accessibility drag from other sources and instead provide a direct + // action for move/add to homescreen. + // When a accessible drag is started by the folder, we only allow rearranging withing the + // folder. + boolean addNewPage = !(options.isAccessibleDrag && dragObject.dragSource != this); + + if (addNewPage) { mDeferRemoveExtraEmptyScreen = false; addExtraEmptyScreenOnDrag(); + + if (dragObject.dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET + && dragObject.dragSource != this) { + // When dragging a widget from different source, move to a page which has + // enough space to place this widget (after rearranging/resizing). We special case + // widgets as they cannot be placed inside a folder. + // Start at the current page and search right (on LTR) until finding a page with + // enough space. Since an empty screen is the furthest right, a page must be found. + int currentPage = getPageNearestToCenterOfScreen(); + for (int pageIndex = currentPage; pageIndex < getPageCount(); pageIndex++) { + CellLayout page = (CellLayout) getPageAt(pageIndex); + if (page.hasReorderSolution(dragObject.dragInfo)) { + setCurrentPage(pageIndex); + break; + } + } + } } - } - public void setAddNewPageOnDrag(boolean addPage) { - mAddNewPageOnDrag = addPage; + if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) { + // Always enter the spring loaded mode + mLauncher.enterSpringLoadedDragMode(); + } } public void deferRemoveExtraEmptyScreen() { @@ -566,7 +594,31 @@ public class Workspace extends PagedView } // Add the first page CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0); - + if (FeatureFlags.PULLDOWN_SEARCH) { + firstPage.setOnTouchListener(new VerticalFlingDetector(mLauncher) { + // detect fling when touch started from empty space + @Override + public boolean onTouch(View v, MotionEvent ev) { + if (workspaceInModalState()) return false; + if (shouldConsumeTouch(v)) return true; + if (super.onTouch(v, ev)) { + mLauncher.startSearch("", false, null, false); + } + return false; + } + }); + firstPage.setOnInterceptTouchListener(new VerticalFlingDetector(mLauncher) { + // detect fling when touch started from on top of the icons + @Override + public boolean onTouch(View v, MotionEvent ev) { + if (shouldConsumeTouch(v)) return true; + if (super.onTouch(v, ev)) { + mLauncher.startSearch("", false, null, false); + } + return false; + } + }); + } // Always add a QSB on the first screen. if (qsb == null) { // In transposed layout, we add the QSB in the Grid. As workspace does not touch the @@ -600,8 +652,8 @@ public class Workspace extends PagedView ViewGroup.LayoutParams lp = qsbContainer.getLayoutParams(); if (cellHeight > 0 && lp.height != cellHeight) { lp.height = cellHeight; + qsbContainer.setLayoutParams(lp); } - qsbContainer.setLayoutParams(lp); } } @@ -658,7 +710,6 @@ public class Workspace extends PagedView // created CellLayout. CellLayout newScreen = (CellLayout) mLauncher.getLayoutInflater().inflate( R.layout.workspace_screen, this, false /* attachToRoot */); - newScreen.setOnLongClickListener(mLongClickListener); newScreen.setOnClickListener(mLauncher); newScreen.setSoundEffectsEnabled(false); @@ -1144,6 +1195,10 @@ public class Workspace extends PagedView @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouch(View v, MotionEvent event) { + return shouldConsumeTouch(v); + } + + private boolean shouldConsumeTouch(View v) { return (workspaceInModalState() || !isFinishedSwitchingState()) || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage); } @@ -1698,26 +1753,6 @@ public class Workspace extends PagedView } } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Override - public void enableAccessibleDrag(boolean enable) { - for (int i = 0; i < getChildCount(); i++) { - CellLayout child = (CellLayout) getChildAt(i); - child.enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG); - } - - if (enable) { - // We need to allow our individual children to become click handlers in this case - setOnClickListener(null); - } else { - // Reset our click listener - setOnClickListener(mLauncher); - } - mLauncher.getDropTargetBar().enableAccessibleDrag(enable); - mLauncher.getHotseat().getLayout() - .enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG); - } - public boolean hasCustomContent() { return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID); } @@ -1850,19 +1885,6 @@ public class Workspace extends PagedView } @Override - protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { - if (!mLauncher.isAppsViewVisible()) { - final Folder openFolder = getOpenFolder(); - if (openFolder != null) { - return openFolder.requestFocus(direction, previouslyFocusedRect); - } else { - return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); - } - } - return false; - } - - @Override public int getDescendantFocusability() { if (workspaceInModalState()) { return ViewGroup.FOCUS_BLOCK_DESCENDANTS; @@ -1870,18 +1892,6 @@ public class Workspace extends PagedView return super.getDescendantFocusability(); } - @Override - public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { - if (!mLauncher.isAppsViewVisible()) { - final Folder openFolder = getOpenFolder(); - if (openFolder != null) { - openFolder.addFocusables(views, direction); - } else { - super.addFocusables(views, direction, focusableMode); - } - } - } - public boolean workspaceInModalState() { return mState != State.NORMAL; } @@ -1999,23 +2009,8 @@ public class Workspace extends PagedView position[0], position[1], 0, null); } - public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) { - // Find a page that has enough space to place this widget (after rearranging/resizing). - // Start at the current page and search right (on LTR) until finding a page with enough - // space. Since an empty screen is the furthest right, a page must be found. - int currentPageInOverview = getPageNearestToCenterOfScreen(); - for (int pageIndex = currentPageInOverview; pageIndex < getPageCount(); pageIndex++) { - CellLayout page = (CellLayout) getPageAt(pageIndex); - if (page.hasReorderSolution(info)) { - setCurrentPage(pageIndex); - break; - } - } - - int[] size = estimateItemSize(info, false); - - // The outline is used to visualize where the item will land if dropped - mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha); + public void prepareDragWithProvider(DragPreviewProvider outlineProvider) { + mOutlineProvider = outlineProvider; } public void exitWidgetResizeMode() { @@ -2271,40 +2266,7 @@ public class Workspace extends PagedView return null; } - /** - * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. - * Responsibility for the bitmap is transferred to the caller. - */ - private Bitmap createDragOutline(Bitmap orig, int padding, int w, int h, - boolean clipAlpha) { - final int outlineColor = getResources().getColor(R.color.outline_color); - final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); - mCanvas.setBitmap(b); - - Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight()); - float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(), - (h - padding) / (float) orig.getHeight()); - int scaledWidth = (int) (scaleFactor * orig.getWidth()); - int scaledHeight = (int) (scaleFactor * orig.getHeight()); - Rect dst = new Rect(0, 0, scaledWidth, scaledHeight); - - // center the image - dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2); - - mCanvas.drawBitmap(orig, src, dst, null); - mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor, - clipAlpha); - mCanvas.setBitmap(null); - - return b; - } - - public void startDrag(CellLayout.CellInfo cellInfo) { - startDrag(cellInfo, false); - } - - @Override - public void startDrag(CellLayout.CellInfo cellInfo, boolean accessible) { + public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) { View child = cellInfo.cell; // Make sure the drag was started by a long press as opposed to a long click. @@ -2317,10 +2279,25 @@ public class Workspace extends PagedView CellLayout layout = (CellLayout) child.getParent().getParent(); layout.prepareChildForDrag(child); - beginDragShared(child, this, accessible); + if (options.isAccessibleDrag) { + mDragController.addDragListener(new AccessibileDragListenerAdapter( + this, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG) { + @Override + protected void enableAccessibleDrag(boolean enable) { + super.enableAccessibleDrag(enable); + setEnableForLayout(mLauncher.getHotseat().getLayout(),enable); + + // We need to allow our individual children to become click handlers in this + // case, so temporarily unset the click handlers. + setOnClickListener(enable ? null : mLauncher); + } + }); + } + + beginDragShared(child, this, options); } - public void beginDragShared(View child, DragSource source, boolean accessible) { + public void beginDragShared(View child, DragSource source, DragOptions options) { Object dragObject = child.getTag(); if (!(dragObject instanceof ItemInfo)) { String msg = "Drag started with a view that has no tag set. This " @@ -2328,20 +2305,17 @@ public class Workspace extends PagedView + "View: " + child + " tag: " + child.getTag(); throw new IllegalStateException(msg); } - beginDragShared(child, source, accessible, (ItemInfo) dragObject, - new DragPreviewProvider(child)); + beginDragShared(child, source, (ItemInfo) dragObject, + new DragPreviewProvider(child), options); } - public DragView beginDragShared(View child, DragSource source, boolean accessible, - ItemInfo dragObject, DragPreviewProvider previewProvider) { + public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject, + DragPreviewProvider previewProvider, DragOptions dragOptions) { child.clearFocus(); child.setPressed(false); + mOutlineProvider = previewProvider; - // The outline is used to visualize where the item will land if dropped - mDragOutline = previewProvider.createDragOutline(mCanvas); - - mLauncher.onDragStarted(child); // The drag bitmap follows the touch point around on the screen final Bitmap b = previewProvider.createDragBitmap(mCanvas); int halfPadding = previewProvider.previewPadding / 2; @@ -2381,15 +2355,9 @@ public class Workspace extends PagedView } DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, - dragObject, DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, - dragRect, scale, accessible); + dragObject, dragVisualizeOffset, dragRect, scale, dragOptions); dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor()); - b.recycle(); - - if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) { - mLauncher.enterSpringLoadedDragMode(); - } return dv; } @@ -2966,7 +2934,7 @@ public class Workspace extends PagedView private void cleanupAddToFolder() { if (mDragOverFolderIcon != null) { - mDragOverFolderIcon.onDragExit(null); + mDragOverFolderIcon.onDragExit(); mDragOverFolderIcon = null; } } @@ -3180,7 +3148,7 @@ public class Workspace extends PagedView item.spanY, child, mTargetCell); if (!nearestDropOccupied) { - mDragTargetLayout.visualizeDropLocation(child, mDragOutline, + mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider, mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, d); } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER) && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX || @@ -3325,7 +3293,7 @@ public class Workspace extends PagedView } boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY; - mDragTargetLayout.visualizeDropLocation(child, mDragOutline, + mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider, mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, dragObject); } } @@ -3717,7 +3685,7 @@ public class Workspace extends PagedView && mDragInfo.cell != null) { mDragInfo.cell.setVisibility(VISIBLE); } - mDragOutline = null; + mOutlineProvider = null; mDragInfo = null; if (!isFlingToDelete) { diff --git a/src/com/android/launcher3/accessibility/AccessibileDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibileDragListenerAdapter.java new file mode 100644 index 000000000..62a9a6d19 --- /dev/null +++ b/src/com/android/launcher3/accessibility/AccessibileDragListenerAdapter.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 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.accessibility; + +import android.view.ViewGroup; + +import com.android.launcher3.CellLayout; +import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.Launcher; +import com.android.launcher3.dragndrop.DragController.DragListener; +import com.android.launcher3.dragndrop.DragOptions; + +/** + * Utility listener to enable/disable accessibility drag flags for a ViewGroup + * containing CellLayouts + */ +public class AccessibileDragListenerAdapter implements DragListener { + + private final ViewGroup mViewGroup; + private final int mDragType; + + /** + * @param parent + * @param dragType either {@link CellLayout#WORKSPACE_ACCESSIBILITY_DRAG} or + * {@link CellLayout#FOLDER_ACCESSIBILITY_DRAG} + */ + public AccessibileDragListenerAdapter(ViewGroup parent, int dragType) { + mViewGroup = parent; + mDragType = dragType; + } + + @Override + public void onDragStart(DragObject dragObject, DragOptions options) { + enableAccessibleDrag(true); + } + + @Override + public void onDragEnd() { + enableAccessibleDrag(false); + Launcher.getLauncher(mViewGroup.getContext()).getDragController().removeDragListener(this); + } + + protected void enableAccessibleDrag(boolean enable) { + for (int i = 0; i < mViewGroup.getChildCount(); i++) { + setEnableForLayout((CellLayout) mViewGroup.getChildAt(i), enable); + } + } + + protected final void setEnableForLayout(CellLayout layout, boolean enable) { + layout.enableAccessibleDrag(enable, mDragType); + } +} diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 0562cf54b..173aad044 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -22,6 +22,8 @@ import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DragSource; +import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.folder.Folder; import com.android.launcher3.FolderInfo; import com.android.launcher3.InfoDropTarget; @@ -73,7 +75,6 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme @Thunk final Launcher mLauncher; private DragInfo mDragInfo = null; - private AccessibilityDragSource mDragSource = null; public LauncherAccessibilityDelegate(Launcher launcher) { mLauncher = launcher; @@ -372,26 +373,25 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme Folder folder = workspace.getOpenFolder(); if (folder != null) { - if (folder.getItemsInReadingOrder().contains(item)) { - mDragSource = folder; - } else { + if (!folder.getItemsInReadingOrder().contains(item)) { mLauncher.closeFolder(); + folder = null; } } - if (mDragSource == null) { - mDragSource = workspace; - } - mDragSource.enableAccessibleDrag(true); - mDragSource.startDrag(cellInfo, true); - if (mLauncher.getDragController().isDragging()) { - mLauncher.getDragController().addDragListener(this); + mLauncher.getDragController().addDragListener(this); + + DragOptions options = new DragOptions(); + options.isAccessibleDrag = true; + if (folder != null) { + folder.startDrag(cellInfo.cell, options); + } else { + workspace.startDrag(cellInfo, options); } } - @Override - public void onDragStart(DragSource source, ItemInfo info, int dragAction) { + public void onDragStart(DragObject dragObject, DragOptions options) { // No-op } @@ -399,16 +399,6 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme public void onDragEnd() { mLauncher.getDragController().removeDragListener(this); mDragInfo = null; - if (mDragSource != null) { - mDragSource.enableAccessibleDrag(false); - mDragSource = null; - } - } - - public static interface AccessibilityDragSource { - void startDrag(CellLayout.CellInfo cellInfo, boolean accessible); - - void enableAccessibleDrag(boolean enable); } /** diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java index ff70279ea..0baa8f3db 100644 --- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java @@ -25,6 +25,7 @@ import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.shortcuts.DeepShortcutView; import java.util.ArrayList; @@ -46,7 +47,10 @@ public class ShortcutMenuAccessibilityDelegate extends LauncherAccessibilityDele @Override public boolean performAction(View host, ItemInfo item, int action) { if (action == ADD_TO_WORKSPACE) { - final ShortcutInfo info = (ShortcutInfo) item; + if (!(host.getParent() instanceof DeepShortcutView)) { + return false; + } + final ShortcutInfo info = ((DeepShortcutView) host.getParent()).getFinalInfo(); final int[] coordinates = new int[2]; final long screenId = findSpaceOnWorkspace(item, coordinates); Runnable onComplete = new Runnable() { diff --git a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java index dafa73fec..cfd07e658 100644 --- a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java +++ b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java @@ -96,14 +96,14 @@ public class AllAppsBackgroundDrawable extends Drawable { public AllAppsBackgroundDrawable(Context context) { Resources res = context.getResources(); mHand = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_hand, - 0.575f, 0.1f, Gravity.CENTER_HORIZONTAL); + 0.575f, 0.f, Gravity.CENTER_HORIZONTAL); mIcons = new TransformedImageDrawable[4]; mIcons[0] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_1, 0.375f, 0, Gravity.CENTER_HORIZONTAL); mIcons[1] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_2, - 0.3125f, 0.25f, Gravity.CENTER_HORIZONTAL); + 0.3125f, 0.2f, Gravity.CENTER_HORIZONTAL); mIcons[2] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_3, - 0.475f, 0.4f, Gravity.CENTER_HORIZONTAL); + 0.475f, 0.26f, Gravity.CENTER_HORIZONTAL); mIcons[3] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_4, 0.7f, 0.125f, Gravity.CENTER_HORIZONTAL); mWidth = res.getDimensionPixelSize(R.dimen.all_apps_background_canvas_width); diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index a8e2140c2..290accb1e 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -25,6 +25,7 @@ import android.text.Selection; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; +import android.text.TextUtils; import android.text.method.TextKeyListener; import android.util.AttributeSet; import android.view.KeyEvent; @@ -48,10 +49,10 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.folder.Folder; import com.android.launcher3.graphics.TintedDrawableSpan; import com.android.launcher3.keyboard.FocusedItemDecorator; -import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; import com.android.launcher3.util.ComponentKey; @@ -277,6 +278,12 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc if (mAppsRecyclerView.getScrollBar().isNearThumb(point[0], point[1])) { return false; } + + // IF a shortcuts container is open, container should not be pulled down. + if (mLauncher.getOpenShortcutsContainer() != null) { + return false; + } + // IF scroller is at the very top OR there is no scroll bar because there is probably not // enough items to scroll, THEN it's okay for the container to be pulled down. if (mAppsRecyclerView.getScrollBar().getThumbOffset().y <= 0) { @@ -539,7 +546,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc if (!mLauncher.isDraggingEnabled()) return false; // Start the drag - mLauncher.getWorkspace().beginDragShared(v, this, false); + mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions()); // Enter spring loaded mode mLauncher.enterSpringLoadedDragMode(); @@ -707,4 +714,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) { targetParent.containerType = mAppsRecyclerView.getContainerType(v); } + + public boolean shouldRestoreImeState() { + return !TextUtils.isEmpty(mSearchInput.getText()); + } } diff --git a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java index 9a48367cd..365ab3185 100644 --- a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java +++ b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java @@ -127,10 +127,9 @@ public abstract class AllAppsSearchBarController @Override public boolean onBackKey() { - // Only hide the search field if there is no query, or if there - // are no filtered results + // Only hide the search field if there is no query String query = Utilities.trim(mInput.getEditableText().toString()); - if (query.isEmpty() || mApps.hasNoFilteredResults()) { + if (query.isEmpty()) { reset(); return true; } @@ -163,8 +162,7 @@ public abstract class AllAppsSearchBarController * Focuses the search field to handle key events. */ public void focusSearchField() { - mInput.requestFocus(); - mInputMethodManager.showSoftInput(mInput, InputMethodManager.SHOW_IMPLICIT); + mInput.showKeyboard(); } /** diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 41d639379..1719b0594 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -6,6 +6,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; +import android.support.v4.content.ContextCompat; import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.util.Log; import android.view.MotionEvent; @@ -101,7 +102,7 @@ public class AllAppsTransitionController implements TouchController, VerticalPul R.dimen.all_apps_bezel_swipe_height); mEvaluator = new ArgbEvaluator(); - mAllAppsBackgroundColor = l.getColor(R.color.all_apps_container_color); + mAllAppsBackgroundColor = ContextCompat.getColor(l, R.color.all_apps_container_color); } @Override diff --git a/src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java index 10740ec77..ac22dd279 100644 --- a/src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java +++ b/src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java @@ -22,15 +22,12 @@ import com.android.launcher3.util.ComponentKey; import java.util.ArrayList; import java.util.List; -import java.util.regex.Pattern; /** * The default search implementation. */ public class DefaultAppSearchAlgorithm { - private static final Pattern SPLIT_PATTERN = Pattern.compile("[\\s|\\p{javaSpaceChar}]+"); - private final List<AppInfo> mApps; protected final Handler mResultHandler; @@ -61,34 +58,79 @@ public class DefaultAppSearchAlgorithm { // Do an intersection of the words in the query and each title, and filter out all the // apps that don't match all of the words in the query. final String queryTextLower = query.toLowerCase(); - final String[] queryWords = SPLIT_PATTERN.split(queryTextLower); - final ArrayList<ComponentKey> result = new ArrayList<>(); for (AppInfo info : mApps) { - if (matches(info, queryWords)) { + if (matches(info, queryTextLower)) { result.add(info.toComponentKey()); } } return result; } - protected boolean matches(AppInfo info, String[] queryWords) { + protected boolean matches(AppInfo info, String query) { + int queryLength = query.length(); + String title = info.title.toString(); - String[] words = SPLIT_PATTERN.split(title.toLowerCase()); - for (int qi = 0; qi < queryWords.length; qi++) { - boolean foundMatch = false; - for (int i = 0; i < words.length; i++) { - if (words[i].startsWith(queryWords[qi])) { - foundMatch = true; - break; - } + int titleLength = title.length(); + + if (titleLength < queryLength || queryLength <= 0) { + return false; + } + + int lastType; + int thisType = Character.UNASSIGNED; + int nextType = Character.getType(title.codePointAt(0)); + + int end = titleLength - queryLength; + for (int i = 0; i <= end; i++) { + lastType = thisType; + thisType = nextType; + nextType = i < (titleLength - 1) ? + Character.getType(title.codePointAt(i + 1)) : Character.UNASSIGNED; + if (isBreak(thisType, lastType, nextType) && + title.substring(i, i + queryLength).equalsIgnoreCase(query)) { + return true; } - if (!foundMatch) { - // If there is a word in the query that does not match any words in this - // title, so skip it. + } + return false; + } + + /** + * Returns true if the current point should be a break point. Following cases + * are considered as break points: + * 1) Any non space character after a space character + * 2) Any digit after a non-digit character + * 3) Any capital character after a digit or small character + * 4) Any capital character before a small character + */ + protected boolean isBreak(int thisType, int prevType, int nextType) { + switch (thisType) { + case Character.UPPERCASE_LETTER: + if (nextType == Character.UPPERCASE_LETTER) { + return true; + } + // Follow through + case Character.TITLECASE_LETTER: + // Break point if previous was not a upper case + return prevType != Character.UPPERCASE_LETTER; + case Character.LOWERCASE_LETTER: + // Break point if previous was not a letter. + return prevType > Character.OTHER_LETTER; + case Character.DECIMAL_DIGIT_NUMBER: + case Character.LETTER_NUMBER: + case Character.OTHER_NUMBER: + // Break point if previous was not a number + return !(prevType == Character.DECIMAL_DIGIT_NUMBER + || prevType == Character.LETTER_NUMBER + || prevType == Character.OTHER_NUMBER); + case Character.MATH_SYMBOL: + case Character.CURRENCY_SYMBOL: + case Character.OTHER_PUNCTUATION: + case Character.DASH_PUNCTUATION: + // Always a break point for a symbol + return true; + default: return false; - } } - return true; } } diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java index 29ed5d9ba..a5f8dd296 100644 --- a/src/com/android/launcher3/compat/UserManagerCompat.java +++ b/src/com/android/launcher3/compat/UserManagerCompat.java @@ -32,8 +32,12 @@ public abstract class UserManagerCompat { public static UserManagerCompat getInstance(Context context) { synchronized (sInstanceLock) { if (sInstance == null) { - if (Utilities.isNycOrAbove()) { + if (Utilities.isNycMR1OrAbove()) { + sInstance = new UserManagerCompatVNMr1(context.getApplicationContext()); + } else if (Utilities.isNycOrAbove()) { sInstance = new UserManagerCompatVN(context.getApplicationContext()); + } else if (Utilities.ATLEAST_MARSHMALLOW) { + sInstance = new UserManagerCompatVM(context.getApplicationContext()); } else if (Utilities.ATLEAST_LOLLIPOP) { sInstance = new UserManagerCompatVL(context.getApplicationContext()); } else if (Utilities.ATLEAST_JB_MR1) { @@ -58,4 +62,6 @@ public abstract class UserManagerCompat { public abstract long getUserCreationTime(UserHandleCompat user); public abstract boolean isQuietModeEnabled(UserHandleCompat user); public abstract boolean isUserUnlocked(UserHandleCompat user); + + public abstract boolean isDemoUser(); } diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java index e678ffa3d..9bd4567a1 100644 --- a/src/com/android/launcher3/compat/UserManagerCompatV16.java +++ b/src/com/android/launcher3/compat/UserManagerCompatV16.java @@ -60,4 +60,9 @@ public class UserManagerCompatV16 extends UserManagerCompat { public boolean isUserUnlocked(UserHandleCompat user) { return true; } + + @Override + public boolean isDemoUser() { + return false; + } } diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java index c53d702b7..2552b0c2c 100644 --- a/src/com/android/launcher3/compat/UserManagerCompatVL.java +++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java @@ -1,4 +1,3 @@ - /* * Copyright (C) 2014 The Android Open Source Project * @@ -94,9 +93,6 @@ public class UserManagerCompatVL extends UserManagerCompatV17 { @Override public long getUserCreationTime(UserHandleCompat user) { - if (Utilities.ATLEAST_MARSHMALLOW) { - return mUserManager.getUserCreationTime(user.getUser()); - } SharedPreferences prefs = Utilities.getPrefs(mContext); String key = USER_CREATION_TIME_KEY + getSerialNumberForUser(user); if (!prefs.contains(key)) { diff --git a/src/com/android/launcher3/compat/UserManagerCompatVM.java b/src/com/android/launcher3/compat/UserManagerCompatVM.java new file mode 100644 index 000000000..81d67ea43 --- /dev/null +++ b/src/com/android/launcher3/compat/UserManagerCompatVM.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 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.compat; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; + +@TargetApi(Build.VERSION_CODES.M) +public class UserManagerCompatVM extends UserManagerCompatVL { + + UserManagerCompatVM(Context context) { + super(context); + } + + @Override + public long getUserCreationTime(UserHandleCompat user) { + return mUserManager.getUserCreationTime(user.getUser()); + } +} diff --git a/src/com/android/launcher3/compat/UserManagerCompatVN.java b/src/com/android/launcher3/compat/UserManagerCompatVN.java index 771d141c3..4edac0522 100644 --- a/src/com/android/launcher3/compat/UserManagerCompatVN.java +++ b/src/com/android/launcher3/compat/UserManagerCompatVN.java @@ -20,10 +20,10 @@ import android.annotation.TargetApi; import android.content.Context; import android.os.Build; -@TargetApi(Build.VERSION_CODES.N) -public class UserManagerCompatVN extends UserManagerCompatVL { +import com.android.launcher3.Utilities; - private static final String TAG = "UserManagerCompatVN"; +@TargetApi(Build.VERSION_CODES.N) +public class UserManagerCompatVN extends UserManagerCompatVM { UserManagerCompatVN(Context context) { super(context); @@ -36,12 +36,7 @@ public class UserManagerCompatVN extends UserManagerCompatVL { @Override public boolean isUserUnlocked(UserHandleCompat user) { - // TODO: Remove the try-catch block when the API permission has been relaxed (b/30475753) - try { - return mUserManager.isUserUnlocked(user.getUser()); - } catch (RuntimeException e) { - return !isQuietModeEnabled(user); - } + return mUserManager.isUserUnlocked(user.getUser()); } } diff --git a/src/com/android/launcher3/compat/UserManagerCompatVNMr1.java b/src/com/android/launcher3/compat/UserManagerCompatVNMr1.java new file mode 100644 index 000000000..3f64bc863 --- /dev/null +++ b/src/com/android/launcher3/compat/UserManagerCompatVNMr1.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 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.compat; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; + +@TargetApi(Build.VERSION_CODES.N_MR1) +public class UserManagerCompatVNMr1 extends UserManagerCompatVN { + + UserManagerCompatVNMr1(Context context) { + super(context); + } + + @Override + public boolean isDemoUser() { + return mUserManager.isDemoUser(); + } +} diff --git a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java new file mode 100644 index 000000000..156941a29 --- /dev/null +++ b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 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.dragndrop; + +import android.content.Context; +import android.view.View; + +import com.android.launcher3.DragSource; +import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.userevent.nano.LauncherLogProto.Target; + +/** + * DragSource used when the drag started at another window. + */ +public class AnotherWindowDragSource implements DragSource { + + private final Context mContext; + + AnotherWindowDragSource(Context context) { + mContext = context; + } + + @Override + public boolean supportsFlingToDelete() { + return false; + } + + @Override + public boolean supportsAppInfoDropTarget() { + return false; + } + + @Override + public boolean supportsDeleteDropTarget() { + return false; + } + + @Override + public float getIntrinsicIconScaleFactor() { + return 1; + } + + @Override + public void onFlingToDeleteCompleted() { + } + + @Override + public void onDropCompleted(View target, DragObject d, + boolean isFlingToDelete, boolean success) { + if (!success) { + Launcher.getLauncher(mContext).exitSpringLoadedDragModeDelayed(false, 0, null); + } + + } + + @Override + public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) { + // TODO: Probably log something + } +} diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index b57f5bf06..a93ee9019 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -25,7 +25,6 @@ import android.graphics.PointF; import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; -import android.util.Log; import android.view.DragEvent; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; @@ -57,12 +56,6 @@ import java.util.ArrayList; public class DragController implements DragDriver.EventListener, TouchController { private static final String TAG = "Launcher.DragController"; - /** Indicates the drag is a move. */ - public static int DRAG_ACTION_MOVE = 0; - - /** Indicates the drag is a copy. */ - public static int DRAG_ACTION_COPY = 1; - public static final int SCROLL_DELAY = 500; public static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150; @@ -91,8 +84,8 @@ public class DragController implements DragDriver.EventListener, TouchController */ private DragDriver mDragDriver = null; - /** Whether or not an accessible drag operation is in progress. */ - private boolean mIsAccessibleDrag; + /** Options controlling the drag behavior. */ + private DragOptions mOptions; /** X coordinate of the down event. */ private int mMotionDownX; @@ -145,12 +138,10 @@ public class DragController implements DragDriver.EventListener, TouchController /** * A drag has begun * - * @param source An object representing where the drag originated - * @param info The data associated with the object that is being dragged - * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE} - * or {@link DragController#DRAG_ACTION_COPY} + * @param dragObject The object being dragged + * @param options Options used to start the drag */ - void onDragStart(DragSource source, ItemInfo info, int dragAction); + void onDragStart(DropTarget.DragObject dragObject, DragOptions options); /** * The drag has ended @@ -160,8 +151,6 @@ public class DragController implements DragDriver.EventListener, TouchController /** * Used to create a new DragLayer from XML. - * - * @param context The application's context. */ public DragController(Launcher launcher) { Resources r = launcher.getResources(); @@ -183,11 +172,9 @@ public class DragController implements DragDriver.EventListener, TouchController * @param source An object representing where the drag originated * @param dragInfo The data associated with the object that is being dragged * @param viewImageBounds the position of the image inside the view - * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or - * {@link #DRAG_ACTION_COPY} */ public void startDrag(View v, Bitmap bmp, DragSource source, ItemInfo dragInfo, - Rect viewImageBounds, int dragAction, float initialDragViewScale) { + Rect viewImageBounds, float initialDragViewScale, DragOptions options) { int[] loc = mCoordinatesTemp; mLauncher.getDragLayer().getLocationInDragLayer(v, loc); int dragLayerX = loc[0] + viewImageBounds.left @@ -195,12 +182,8 @@ public class DragController implements DragDriver.EventListener, TouchController int dragLayerY = loc[1] + viewImageBounds.top + (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2); - startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, - null, initialDragViewScale, false); - - if (dragAction == DRAG_ACTION_MOVE) { - v.setVisibility(View.GONE); - } + startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, null, + null, initialDragViewScale, options); } /** @@ -212,15 +195,12 @@ public class DragController implements DragDriver.EventListener, TouchController * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap. * @param source An object representing where the drag originated * @param dragInfo The data associated with the object that is being dragged - * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or - * {@link #DRAG_ACTION_COPY} * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. * Makes dragging feel more precise, e.g. you can clip out a transparent border - * @param accessible whether this drag should occur in accessibility mode */ public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY, - DragSource source, ItemInfo dragInfo, int dragAction, Point dragOffset, Rect dragRegion, - float initialDragViewScale, boolean accessible) { + DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion, + float initialDragViewScale, DragOptions options) { if (PROFILE_DRAWING_DURING_DRAG) { android.os.Debug.startMethodTracing("Launcher"); } @@ -232,8 +212,10 @@ public class DragController implements DragDriver.EventListener, TouchController } mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0); - for (DragListener listener : mListeners) { - listener.onDragStart(source, dragInfo, dragAction); + mOptions = options; + if (mOptions.systemDndStartPoint != null) { + mMotionDownX = mOptions.systemDndStartPoint.x; + mMotionDownY = mOptions.systemDndStartPoint.y; } final int registrationX = mMotionDownX - dragLayerX; @@ -242,7 +224,6 @@ public class DragController implements DragDriver.EventListener, TouchController final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; - mIsAccessibleDrag = accessible; mLastDropTarget = null; mDragObject = new DropTarget.DragObject(); @@ -254,7 +235,7 @@ public class DragController implements DragDriver.EventListener, TouchController registrationY, initialDragViewScale, scaleDps); mDragObject.dragComplete = false; - if (mIsAccessibleDrag) { + if (mOptions.isAccessibleDrag) { // For an accessible drag, we assume the view is being dragged from the center. mDragObject.xOffset = b.getWidth() / 2; mDragObject.yOffset = b.getHeight() / 2; @@ -264,7 +245,7 @@ public class DragController implements DragDriver.EventListener, TouchController mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop); mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView); - mDragDriver = DragDriver.create(this, dragInfo, dragView); + mDragDriver = DragDriver.create(mLauncher, this, mDragObject, mOptions); } mDragObject.dragSource = source; @@ -282,6 +263,11 @@ public class DragController implements DragDriver.EventListener, TouchController mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); dragView.show(mMotionDownX, mMotionDownY); mDistanceSinceScroll = 0; + + for (DragListener listener : new ArrayList<>(mListeners)) { + listener.onDragStart(mDragObject, mOptions); + } + mLastTouch[0] = mMotionDownX; mLastTouch[1] = mMotionDownY; handleMoveEvent(mMotionDownX, mMotionDownY); @@ -308,7 +294,11 @@ public class DragController implements DragDriver.EventListener, TouchController } public boolean isDragging() { - return mDragDriver != null || mIsAccessibleDrag; + return mDragDriver != null || (mOptions != null && mOptions.isAccessibleDrag); + } + + public boolean isExternalDrag() { + return (mOptions != null && mOptions.systemDndStartPoint != null); } /** @@ -343,7 +333,7 @@ public class DragController implements DragDriver.EventListener, TouchController private void endDrag() { if (isDragging()) { mDragDriver = null; - mIsAccessibleDrag = false; + mOptions = null; clearScrollRunnable(); boolean isDeferred = false; if (mDragObject.dragView != null) { @@ -422,10 +412,6 @@ public class DragController implements DragDriver.EventListener, TouchController @Override public void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride) { - final int[] dragLayerPos = getClampedDragLayerPos(x, y); - final int dragLayerX = dragLayerPos[0]; - final int dragLayerY = dragLayerPos[1]; - DropTarget dropTarget; PointF vec = null; @@ -454,14 +440,7 @@ public class DragController implements DragDriver.EventListener, TouchController * Call this from a drag source view. */ public boolean onInterceptTouchEvent(MotionEvent ev) { - @SuppressWarnings("all") // suppress dead code warning - final boolean debug = false; - if (debug) { - Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " Dragging=" - + (mDragDriver != null)); - } - - if (mIsAccessibleDrag) { + if (mOptions != null && mOptions.isAccessibleDrag) { return false; } @@ -604,7 +583,7 @@ public class DragController implements DragDriver.EventListener, TouchController * Call this from a drag source view. */ public boolean onTouchEvent(MotionEvent ev) { - if (mDragDriver == null || mIsAccessibleDrag) { + if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) { return false; } diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java index 2164708ba..4db8c07d5 100644 --- a/src/com/android/launcher3/dragndrop/DragDriver.java +++ b/src/com/android/launcher3/dragndrop/DragDriver.java @@ -17,18 +17,19 @@ package com.android.launcher3.dragndrop; import android.content.ClipData; +import android.content.ClipDescription; +import android.content.Context; import android.content.Intent; -import android.graphics.Canvas; -import android.graphics.Point; import android.view.DragEvent; import android.view.MotionEvent; -import android.view.View; -import com.android.launcher3.AnotherWindowDropTarget; import com.android.launcher3.DropTarget; -import com.android.launcher3.ItemInfo; +import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.InstallShortcutReceiver; +import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; + +import java.util.ArrayList; /** * Base class for driving a drag/drop operation. @@ -50,7 +51,7 @@ public abstract class DragDriver { /** * Handles ending of the DragView animation. */ - public abstract void onDragViewAnimationEnd(); + public void onDragViewAnimationEnd() { } public boolean onTouchEvent(MotionEvent ev) { final int action = ev.getAction(); @@ -89,100 +90,46 @@ public abstract class DragDriver { return true; } - public static DragDriver create( - DragController dragController, ItemInfo dragInfo, DragView dragView) { - if (FeatureFlags.LAUNCHER3_USE_SYSTEM_DRAG_DRIVER && Utilities.isNycOrAbove()) { - return new SystemDragDriver(dragController, dragInfo.getIntent(), dragView); + public static DragDriver create(Context context, DragController dragController, + DragObject dragObject, DragOptions options) { + if (Utilities.isNycOrAbove() && options.systemDndStartPoint != null) { + return new SystemDragDriver(dragController, context, dragObject); } else { return new InternalDragDriver(dragController); } } - -}; +} /** * Class for driving a system (i.e. framework) drag/drop operation. */ class SystemDragDriver extends DragDriver { - /** Intent associated with the drag operation, or null is there no associated intent. */ - private final Intent mDragIntent; - private final DragView mDragView; - boolean mIsFrameworkDragActive = false; + private final DragObject mDragObject; + private final Context mContext; + boolean mReceivedDropEvent = false; float mLastX = 0; float mLastY = 0; - public SystemDragDriver(DragController dragController, Intent dragIntent, DragView dragView) { + public SystemDragDriver(DragController dragController, Context context, DragObject dragObject) { super(dragController); - mDragIntent = dragIntent; - mDragView = dragView; - } - - private static class ShadowBuilder extends View.DragShadowBuilder { - final DragView mDragView; - - public ShadowBuilder(DragView dragView) { - mDragView = dragView; - } - - @Override - public void onProvideShadowMetrics (Point size, Point touch) { - mDragView.provideDragShadowMetrics(size, touch); - } - - @Override - public void onDrawShadow(Canvas canvas) { - mDragView.drawDragShadow(canvas); - } - }; - - @Override - public void onDragViewAnimationEnd() { - // Clip data for the drag operation. If there is an intent, create an intent-based ClipData, - // which will be passed to a global DND. - // If there is no intent, craft a fake ClipData and start a local DND operation; this - // ClipData will be ignored. - final ClipData dragData = mDragIntent != null ? - ClipData.newIntent("", mDragIntent) : - ClipData.newPlainText("", ""); - - View.DragShadowBuilder shadowBuilder = new ShadowBuilder(mDragView); - // TODO: DND flags are in flux, once settled, use the appropriate constant. - final int flagGlobal = 1 << 0; - final int flagOpaque = 1 << 9; - final int flags = (mDragIntent != null ? flagGlobal : 0) | flagOpaque; - - mIsFrameworkDragActive = true; - - if (!mDragView.startDrag(dragData, shadowBuilder, null, flags)) { - mIsFrameworkDragActive = false; - mEventListener.onDriverDragCancel(); - return; - } - - // Starting from this point, the driver takes over showing the drag shadow, so hiding the - // drag view. - mDragView.setVisibility(View.INVISIBLE); + mDragObject = dragObject; + mContext = context; } @Override public boolean onTouchEvent(MotionEvent ev) { - return !mIsFrameworkDragActive && super.onTouchEvent(ev); + return false; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - return !mIsFrameworkDragActive && super.onInterceptTouchEvent(ev); + return false; } @Override public boolean onDragEvent (DragEvent event) { - if (!mIsFrameworkDragActive) { - // We are interested only in drag events started by this driver. - return false; - } - final int action = event.getAction(); switch (action) { @@ -192,8 +139,6 @@ class SystemDragDriver extends DragDriver { return true; case DragEvent.ACTION_DRAG_ENTERED: - mLastX = event.getX(); - mLastY = event.getY(); return true; case DragEvent.ACTION_DRAG_LOCATION: @@ -205,35 +150,66 @@ class SystemDragDriver extends DragDriver { case DragEvent.ACTION_DROP: mLastX = event.getX(); mLastY = event.getY(); - mReceivedDropEvent = true; - return true; + mReceivedDropEvent = + updateInfoFromClipData(event.getClipData(), event.getClipDescription()); + return mReceivedDropEvent; case DragEvent.ACTION_DRAG_EXITED: - mLastX = event.getX(); - mLastY = event.getY(); mEventListener.onDriverDragExitWindow(); return true; case DragEvent.ACTION_DRAG_ENDED: - final boolean dragAccepted = event.getResult(); - final boolean acceptedByAnotherWindow = dragAccepted && !mReceivedDropEvent; - - // When the system drag ends, its drag shadow disappears. Resume showing the drag - // view for the possible final animation. - mDragView.setVisibility(View.VISIBLE); - - final DropTarget dropTargetOverride = acceptedByAnotherWindow ? - new AnotherWindowDropTarget(mDragView.getContext()) : null; - - mEventListener.onDriverDragEnd(mLastX, mLastY, dropTargetOverride); - mIsFrameworkDragActive = false; + if (mReceivedDropEvent) { + mEventListener.onDriverDragEnd(mLastX, mLastY, null); + } else { + mEventListener.onDriverDragCancel(); + } return true; default: return false; } } -}; + + private boolean updateInfoFromClipData(ClipData data, ClipDescription desc) { + if (data == null) { + return false; + } + ArrayList<Intent> intents = new ArrayList<>(); + int itemCount = data.getItemCount(); + for (int i = 0; i < itemCount; i++) { + Intent intent = data.getItemAt(i).getIntent(); + if (intent == null) { + continue; + } + + // Give preference to shortcut intents. + if (!Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) { + intents.add(intent); + continue; + } + ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, intent); + if (info != null) { + mDragObject.dragInfo = info; + return true; + } + return true; + } + + // Try creating shortcuts just using the intent and label + Intent fullIntent = new Intent().putExtra(Intent.EXTRA_SHORTCUT_NAME, desc.getLabel()); + for (Intent intent : intents) { + fullIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent); + ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, fullIntent); + if (info != null) { + mDragObject.dragInfo = info; + return true; + } + } + + return false; + } +} /** * Class for driving an internal (i.e. not using framework) drag/drop operation. @@ -244,8 +220,5 @@ class InternalDragDriver extends DragDriver { } @Override - public void onDragViewAnimationEnd() {} - - @Override public boolean onDragEvent (DragEvent event) { return false; } }; diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index e88e77e20..016347b17 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -21,14 +21,22 @@ import android.animation.AnimatorListenerAdapter; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.TargetApi; +import android.content.ClipDescription; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; +import android.os.Build; import android.util.AttributeSet; +import android.util.Log; import android.view.DragEvent; import android.view.KeyEvent; import android.view.MotionEvent; @@ -45,12 +53,14 @@ import com.android.launcher3.AppWidgetResizeFrame; import com.android.launcher3.CellLayout; import com.android.launcher3.DropTargetBar; import com.android.launcher3.InsettableFrameLayout; +import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetHostView; import com.android.launcher3.PinchToOverviewListener; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; +import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.allapps.AllAppsTransitionController; @@ -62,6 +72,7 @@ import com.android.launcher3.shortcuts.DeepShortcutsContainer; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.TouchController; +import java.net.URISyntaxException; import java.util.ArrayList; /** @@ -100,7 +111,6 @@ public class DragLayer extends InsettableFrameLayout { private TouchCompleteListener mTouchCompleteListener; - private View mOverlayView; private int mTopViewIndex; private int mChildCountOnLastUpdate = -1; @@ -172,20 +182,6 @@ public class DragLayer extends InsettableFrameLayout { ? null : new PinchToOverviewListener(mLauncher); } - public void showOverlayView(View overlayView) { - LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - mOverlayView = overlayView; - addView(overlayView, lp); - - // ensure that the overlay view stays on top. we can't use drawing order for this - // because in API level 16 touch dispatch doesn't respect drawing order. - mOverlayView.bringToFront(); - } - - public void dismissOverlayView() { - removeView(mOverlayView); - } - public boolean isEventOverPageIndicator(MotionEvent ev) { getDescendantRectRelativeToSelf(mLauncher.getWorkspace().getPageIndicator(), mHitRect); return mHitRect.contains((int) ev.getX(), (int) ev.getY()); @@ -358,16 +354,9 @@ public class DragLayer extends InsettableFrameLayout { } private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) { - AccessibilityManager accessibilityManager = (AccessibilityManager) - getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - if (accessibilityManager.isEnabled()) { - int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close; - AccessibilityEvent event = AccessibilityEvent.obtain( - AccessibilityEvent.TYPE_VIEW_FOCUSED); - onInitializeAccessibilityEvent(event); - event.getText().add(getContext().getString(stringId)); - accessibilityManager.sendAccessibilityEvent(event); - } + int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close; + Utilities.sendCustomAccessibilityEvent( + this, AccessibilityEvent.TYPE_VIEW_FOCUSED, getContext().getString(stringId)); } private boolean isInAccessibleDrag() { @@ -377,37 +366,27 @@ public class DragLayer extends InsettableFrameLayout { @Override public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { // Shortcuts can appear above folder - View topView = mLauncher.getOpenShortcutsContainer(); + View topView = mLauncher.getTopFloatingView(); if (topView != null) { - return handleTopViewSendAccessibilityEvent(topView, child, event); - } - - topView = mLauncher.getWorkspace().getOpenFolder(); - if (topView != null) { - return handleTopViewSendAccessibilityEvent(topView, child, event); + if (child == topView) { + return super.onRequestSendAccessibilityEvent(child, event); + } + if (isInAccessibleDrag() && child instanceof DropTargetBar) { + return super.onRequestSendAccessibilityEvent(child, event); + } + // Skip propagating onRequestSendAccessibilityEvent for all other children + // which are not topView + return false; } return super.onRequestSendAccessibilityEvent(child, event); } - private boolean handleTopViewSendAccessibilityEvent( - View topView, View child, AccessibilityEvent event) { - if (child == topView) { - return super.onRequestSendAccessibilityEvent(child, event); - } - if (isInAccessibleDrag() && child instanceof DropTargetBar) { - return super.onRequestSendAccessibilityEvent(child, event); - } - // Skip propagating onRequestSendAccessibilityEvent for all other children - // which are not topView - return false; - } - @Override public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) { - Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); - if (currentFolder != null) { - // Only add the folder as a child for accessibility when it is open - childrenForAccessibility.add(currentFolder); + View topView = mLauncher.getTopFloatingView(); + if (topView != null) { + // Only add the top view as a child for accessibility when it is open + childrenForAccessibility.add(topView); if (isInAccessibleDrag()) { childrenForAccessibility.add(mLauncher.getDropTargetBar()); @@ -463,8 +442,46 @@ public class DragLayer extends InsettableFrameLayout { return false; } + @TargetApi(Build.VERSION_CODES.N) + private void handleSystemDragStart(DragEvent event) { + if (!FeatureFlags.LAUNCHER3_USE_SYSTEM_DRAG_DRIVER || !Utilities.isNycOrAbove()) { + return; + } + if (mLauncher.isWorkspaceLocked()) { + return; + } + + ClipDescription description = event.getClipDescription(); + if (!description.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) { + return; + } + ShortcutInfo info = new ShortcutInfo(); + // Set a dummy intent until we get the final value + info.intent = new Intent(); + + // Since we are not going through the workspace for starting the drag, set drag related + // information on the workspace before starting the drag. + ExternalDragPreviewProvider previewProvider = + new ExternalDragPreviewProvider(mLauncher, info); + mLauncher.getWorkspace().prepareDragWithProvider(previewProvider); + + DragOptions options = new DragOptions(); + options.systemDndStartPoint = new Point((int) event.getX(), (int) event.getY()); + + int halfPadding = previewProvider.previewPadding / 2; + mDragController.startDrag( + Bitmap.createBitmap(1, 1, Config.ARGB_8888), + 0, 0, + new AnotherWindowDragSource(mLauncher), info, + new Point(- halfPadding, halfPadding), + previewProvider.getPreviewBounds(), 1f, options); + } + @Override public boolean onDragEvent (DragEvent event) { + if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) { + handleSystemDragStart(event); + } return mDragController.onDragEvent(event); } @@ -538,7 +555,9 @@ public class DragLayer extends InsettableFrameLayout { @Override public boolean dispatchUnhandledMove(View focused, int direction) { - return mDragController.dispatchUnhandledMove(focused, direction); + // Consume the unhandled move if a container is open, to avoid switching pages underneath. + boolean isContainerOpen = mLauncher.getTopFloatingView() != null; + return isContainerOpen || mDragController.dispatchUnhandledMove(focused, direction); } @Override @@ -788,11 +807,15 @@ public class DragLayer extends InsettableFrameLayout { // If duration < 0, this is a cue to compute the duration based on the distance if (duration < 0) { - duration = res.getInteger(R.integer.config_dropAnimMaxDuration); - if (dist < maxDist) { - duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist); + if (mDragController != null && mDragController.isExternalDrag()) { + duration = 1; + } else { + duration = res.getInteger(R.integer.config_dropAnimMaxDuration); + if (dist < maxDist) { + duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist); + } + duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration)); } - duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration)); } // Fall back to cubic ease out interpolator for the animation if none is specified @@ -903,11 +926,6 @@ public class DragLayer extends InsettableFrameLayout { @Override public void onChildViewAdded(View parent, View child) { super.onChildViewAdded(parent, child); - if (mOverlayView != null) { - // ensure that the overlay view stays on top. we can't use drawing order for this - // because in API level 16 touch dispatch doesn't respect drawing order. - mOverlayView.bringToFront(); - } updateChildIndices(); } @@ -919,11 +937,6 @@ public class DragLayer extends InsettableFrameLayout { @Override public void bringChildToFront(View child) { super.bringChildToFront(child); - if (child != mOverlayView && mOverlayView != null) { - // ensure that the overlay view stays on top. we can't use drawing order for this - // because in API level 16 touch dispatch doesn't respect drawing order. - mOverlayView.bringToFront(); - } updateChildIndices(); } @@ -1062,6 +1075,26 @@ public class DragLayer extends InsettableFrameLayout { return mBackgroundAlpha; } + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + View topView = mLauncher.getTopFloatingView(); + if (topView != null) { + return topView.requestFocus(direction, previouslyFocusedRect); + } else { + return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); + } + } + + @Override + public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { + View topView = mLauncher.getTopFloatingView(); + if (topView != null) { + topView.addFocusables(views, direction); + } else { + super.addFocusables(views, direction, focusableMode); + } + } + public void setTouchCompleteListener(TouchCompleteListener listener) { mTouchCompleteListener = listener; } diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java new file mode 100644 index 000000000..3d52a48c6 --- /dev/null +++ b/src/com/android/launcher3/dragndrop/DragOptions.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2016 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.dragndrop; + +import android.graphics.Point; + +/** + * Set of options to control the drag and drop behavior. + */ +public class DragOptions { + + /** Whether or not an accessible drag operation is in progress. */ + public boolean isAccessibleDrag = false; + + /** Specifies the start location for the system DnD, null when using internal DnD */ + public Point systemDndStartPoint = null; +} diff --git a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java new file mode 100644 index 000000000..6b14be714 --- /dev/null +++ b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 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.dragndrop; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.HolographicOutlineHelper; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.graphics.DragPreviewProvider; + +/** + * Extension of {@link DragPreviewProvider} which provides a dummy outline when drag starts from + * a different window. + * It just draws an empty circle to a placeholder outline. + */ +public class ExternalDragPreviewProvider extends DragPreviewProvider { + + private final Launcher mLauncher; + private final ItemInfo mAddInfo; + + private final int[] mOutlineSize; + + public ExternalDragPreviewProvider(Launcher launcher, ItemInfo addInfo) { + super(null); + mLauncher = launcher; + mAddInfo = addInfo; + + mOutlineSize = mLauncher.getWorkspace().estimateItemSize(mAddInfo, false); + } + + public Rect getPreviewBounds() { + Rect rect = new Rect(); + DeviceProfile dp = mLauncher.getDeviceProfile(); + rect.left = DRAG_BITMAP_PADDING / 2; + rect.top = (mOutlineSize[1] - dp.cellHeightPx) / 2; + rect.right = rect.left + dp.iconSizePx; + rect.bottom = rect.top + dp.iconSizePx; + return rect; + } + + @Override + public Bitmap createDragOutline(Canvas canvas) { + final Bitmap b = Bitmap.createBitmap(mOutlineSize[0], mOutlineSize[1], Bitmap.Config.ALPHA_8); + canvas.setBitmap(b); + + Paint paint = new Paint(); + paint.setColor(Color.WHITE); + paint.setStyle(Paint.Style.FILL); + + // Use 0.9f times the radius for the actual circle to account for icon normalization. + float radius = getPreviewBounds().width() * 0.5f; + canvas.drawCircle(DRAG_BITMAP_PADDING / 2 + radius, + DRAG_BITMAP_PADDING / 2 + radius, radius * 0.9f, paint); + + HolographicOutlineHelper.obtain(mLauncher).applyExpensiveOutlineWithBlur(b, canvas); + canvas.setBitmap(null); + return b; + } +} diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 5450423a2..b64d12c1d 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -42,7 +42,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.inputmethod.EditorInfo; @@ -51,8 +50,8 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.launcher3.Alarm; +import com.android.launcher3.AppInfo; import com.android.launcher3.CellLayout; -import com.android.launcher3.CellLayout.CellInfo; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; @@ -71,12 +70,12 @@ import com.android.launcher3.ShortcutInfo; import com.android.launcher3.UninstallDropTarget.DropTargetSource; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace.ItemOperator; -import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource; +import com.android.launcher3.accessibility.AccessibileDragListenerAdapter; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragController.DragListener; import com.android.launcher3.dragndrop.DragLayer; -import com.android.launcher3.logging.UserEventDispatcher.LaunchSourceProvider; +import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; @@ -92,7 +91,7 @@ import java.util.Comparator; */ public class Folder extends LinearLayout implements DragSource, View.OnClickListener, View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener, - View.OnFocusChangeListener, DragListener, DropTargetSource, AccessibilityDragSource { + View.OnFocusChangeListener, DragListener, DropTargetSource { private static final String TAG = "Launcher.Folder"; /** @@ -165,10 +164,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList @ViewDebug.ExportedProperty(category = "launcher") private boolean mRearrangeOnClose = false; boolean mItemsInvalidated = false; - private ShortcutInfo mCurrentDragInfo; private View mCurrentDragView; private boolean mIsExternalDrag; - boolean mSuppressOnAdd = false; private boolean mDragInProgress = false; private boolean mDeleteFolderOnDropCompleted = false; private boolean mSuppressFolderDeletion = false; @@ -282,10 +279,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList public boolean onLongClick(View v) { // Return if global dragging is not enabled if (!mLauncher.isDraggingEnabled()) return true; - return beginDrag(v, false); + return startDrag(v, new DragOptions()); } - private boolean beginDrag(View v, boolean accessible) { + public boolean startDrag(View v, DragOptions options) { Object tag = v.getTag(); if (tag instanceof ShortcutInfo) { ShortcutInfo item = (ShortcutInfo) tag; @@ -293,35 +290,55 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList return false; } - mLauncher.getWorkspace().beginDragShared(v, this, accessible); - - mCurrentDragInfo = item; mEmptyCellRank = item.rank; mCurrentDragView = v; - mContent.removeItem(mCurrentDragView); - mInfo.remove(mCurrentDragInfo, true); - mDragInProgress = true; - mItemAddedBackToSelfViaIcon = false; + mDragController.addDragListener(this); + if (options.isAccessibleDrag) { + mDragController.addDragListener(new AccessibileDragListenerAdapter( + mContent, CellLayout.FOLDER_ACCESSIBILITY_DRAG) { + + @Override + protected void enableAccessibleDrag(boolean enable) { + super.enableAccessibleDrag(enable); + mFooter.setImportantForAccessibility(enable + ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + : IMPORTANT_FOR_ACCESSIBILITY_AUTO); + } + }); + } + + mLauncher.getWorkspace().beginDragShared(v, this, options); } return true; } @Override - public void startDrag(CellInfo cellInfo, boolean accessible) { - beginDrag(cellInfo.cell, accessible); + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { + if (dragObject.dragSource != this) { + return; + } + + mContent.removeItem(mCurrentDragView); + if (dragObject.dragInfo instanceof ShortcutInfo) { + mItemsInvalidated = true; + + // We do not want to get events for the item being removed, as they will get handled + // when the drop completes + try (SuppressInfoChanges s = new SuppressInfoChanges()) { + mInfo.remove((ShortcutInfo) dragObject.dragInfo, true); + } + } + mDragInProgress = true; + mItemAddedBackToSelfViaIcon = false; } @Override - public void enableAccessibleDrag(boolean enable) { - mLauncher.getDropTargetBar().enableAccessibleDrag(enable); - for (int i = 0; i < mContent.getChildCount(); i++) { - mContent.getPageAt(i).enableAccessibleDrag(enable, CellLayout.FOLDER_ACCESSIBILITY_DRAG); + public void onDragEnd() { + if (mIsExternalDrag && mDragInProgress) { + completeDragExit(); } - - mFooter.setImportantForAccessibility(enable ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS : - IMPORTANT_FOR_ACCESSIBILITY_AUTO); - mLauncher.getWorkspace().setAddNewPageOnDrag(!enable); + mDragController.removeDragListener(this); } public boolean isEditingName() { @@ -352,7 +369,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList LauncherModel.updateItemInDatabase(mLauncher, mInfo); if (commit) { - sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + Utilities.sendCustomAccessibilityEvent( + this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, getContext().getString(R.string.folder_renamed, newTitle)); } @@ -591,7 +609,9 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList openFolderAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + Utilities.sendCustomAccessibilityEvent( + Folder.this, + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, mContent.getAccessibilityDescription()); mState = STATE_ANIMATING; } @@ -650,9 +670,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mContent.verifyVisibleHighResIcons(mContent.getNextPage()); } - public void beginExternalDrag(ShortcutInfo item) { - mCurrentDragInfo = item; - mEmptyCellRank = mContent.allocateRankForNewItem(item); + public void beginExternalDrag() { + mEmptyCellRank = mContent.allocateRankForNewItem(); mIsExternalDrag = true; mDragInProgress = true; @@ -661,28 +680,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mDragController.addDragListener(this); } - @Override - public void onDragStart(DragSource source, ItemInfo info, int dragAction) { } - - @Override - public void onDragEnd() { - if (mIsExternalDrag && mDragInProgress) { - completeDragExit(); - } - mDragController.removeDragListener(this); - } - - @Thunk void sendCustomAccessibilityEvent(int type, String text) { - AccessibilityManager accessibilityManager = (AccessibilityManager) - getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - if (accessibilityManager.isEnabled()) { - AccessibilityEvent event = AccessibilityEvent.obtain(type); - onInitializeAccessibilityEvent(event); - event.getText().add(text); - accessibilityManager.sendAccessibilityEvent(event); - } - } - public void animateClosed() { if (!(getParent() instanceof DragLayer)) return; final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f); @@ -694,7 +691,9 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } @Override public void onAnimationStart(Animator animation) { - sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + Utilities.sendCustomAccessibilityEvent( + Folder.this, + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, getContext().getString(R.string.folder_closed)); mState = STATE_ANIMATING; } @@ -851,9 +850,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } private void clearDragInfo() { - mCurrentDragInfo = null; mCurrentDragView = null; - mSuppressOnAdd = false; mIsExternalDrag = false; } @@ -917,9 +914,9 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mContent.arrangeChildren(views, views.size()); mItemsInvalidated = true; - mSuppressOnAdd = true; - mFolderIcon.onDrop(d); - mSuppressOnAdd = false; + try (SuppressInfoChanges s = new SuppressInfoChanges()) { + mFolderIcon.onDrop(d); + } } if (target != this) { @@ -936,9 +933,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mDeleteFolderOnDropCompleted = false; mDragInProgress = false; mItemAddedBackToSelfViaIcon = false; - mCurrentDragInfo = null; mCurrentDragView = null; - mSuppressOnAdd = false; // Reordering may have occured, and we need to save the new item locations. We do this once // at the end to prevent unnecessary database operations. @@ -1280,7 +1275,14 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mContent.completePendingPageChanges(); View currentDragView; - ShortcutInfo si = mCurrentDragInfo; + final ShortcutInfo si; + if (d.dragInfo instanceof AppInfo) { + // Came from all apps -- make a copy. + si = ((AppInfo) d.dragInfo).makeShortcut(); + } else { + // ShortcutInfo + si = (ShortcutInfo) d.dragInfo; + } if (mIsExternalDrag) { currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank); // Actually move the item in the database if it was an external drag. Call this @@ -1317,11 +1319,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList rearrangeChildren(); // Temporarily suppress the listener, as we did all the work already here. - mSuppressOnAdd = true; - mInfo.add(si, false); - mSuppressOnAdd = false; + try (SuppressInfoChanges s = new SuppressInfoChanges()) { + mInfo.add(si, false); + } + // Clear the drag info, as it is no longer being dragged. - mCurrentDragInfo = null; mDragInProgress = false; if (mContent.getPageCount() > 1) { @@ -1344,10 +1346,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList @Override public void onAdd(ShortcutInfo item) { - // If the item was dropped onto this open folder, we have done the work associated - // with adding the item to the folder, as indicated by mSuppressOnAdd being set - if (mSuppressOnAdd) return; - mContent.createAndAddViewForRank(item, mContent.allocateRankForNewItem(item)); + mContent.createAndAddViewForRank(item, mContent.allocateRankForNewItem()); mItemsInvalidated = true; LauncherModel.addOrMoveItemInDatabase( mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); @@ -1355,9 +1354,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList public void onRemove(ShortcutInfo item) { mItemsInvalidated = true; - // If this item is being dragged from this open folder, we have already handled - // the work associated with removing the item, so we don't have to do anything here. - if (item == mCurrentDragInfo) return; View v = getViewForInfo(item); mContent.removeItem(v); if (mState == STATE_ANIMATING) { @@ -1496,4 +1492,20 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } } }; + + /** + * Temporary resource held while we don't want to handle info changes + */ + private class SuppressInfoChanges implements AutoCloseable { + + SuppressInfoChanges() { + mInfo.removeListener(Folder.this); + } + + @Override + public void close() { + mInfo.addListener(Folder.this); + updateTextViewFocus(); + } + } } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index eebbfe8b7..69c2b0fa3 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -125,8 +125,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { Paint mBgPaint = new Paint(); private Alarm mOpenAlarm = new Alarm(); - @Thunk - ItemInfo mDragInfo; public FolderIcon(Context context, AttributeSet attrs) { super(context, attrs); @@ -195,8 +193,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { return super.onSaveInstanceState(); } - - public Folder getFolder() { return mFolder; } @@ -242,22 +238,11 @@ public class FolderIcon extends FrameLayout implements FolderListener { // Workspace#onDropExternal. mOpenAlarm.setAlarm(ON_OPEN_DELAY); } - mDragInfo = dragInfo; } OnAlarmListener mOnOpenListener = new OnAlarmListener() { public void onAlarm(Alarm alarm) { - ShortcutInfo item; - if (mDragInfo instanceof AppInfo) { - // Came from all apps -- make a copy. - item = ((AppInfo) mDragInfo).makeShortcut(); - item.spanX = 1; - item.spanY = 1; - } else { - // ShortcutInfo - item = (ShortcutInfo) mDragInfo; - } - mFolder.beginExternalDrag(item); + mFolder.beginExternalDrag(); mLauncher.openFolder(FolderIcon.this); } }; @@ -284,7 +269,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION, false, null); // This will animate the dragView (srcView) into the new folder - onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable, null); + onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable); } public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) { @@ -298,18 +283,13 @@ public class FolderIcon extends FrameLayout implements FolderListener { onCompleteRunnable); } - public void onDragExit(Object dragInfo) { - onDragExit(); - } - public void onDragExit() { mBackground.animateToRest(); mOpenAlarm.cancelAlarm(); } private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect, - float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable, - DragObject d) { + float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable) { item.cellX = -1; item.cellY = -1; @@ -379,7 +359,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { item = (ShortcutInfo) d.dragInfo; } mFolder.notifyDrop(); - onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d); + onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable); } private void computePreviewDrawingParams(int drawableSize, int totalSize) { diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index 3df1d2438..1171d488d 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -195,7 +195,7 @@ public class FolderPagedView extends PagedView { * Create space for a new item at the end, and returns the rank for that item. * Also sets the current page to the last page. */ - public int allocateRankForNewItem(ShortcutInfo info) { + public int allocateRankForNewItem() { int rank = getItemCount(); ArrayList<View> views = new ArrayList<>(mFolder.getItemsInReadingOrder()); views.add(rank, null); diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java index b90c2fd39..bc91c15be 100644 --- a/src/com/android/launcher3/graphics/DragPreviewProvider.java +++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java @@ -27,8 +27,8 @@ import android.widget.TextView; import com.android.launcher3.HolographicOutlineHelper; import com.android.launcher3.Launcher; import com.android.launcher3.PreloadIconDrawable; -import com.android.launcher3.R; import com.android.launcher3.Workspace; +import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.folder.FolderIcon; /** @@ -45,6 +45,8 @@ public class DragPreviewProvider { // The padding added to the drag view during the preview generation. public final int previewPadding; + public Bitmap gerenatedDragOutline; + public DragPreviewProvider(View view) { mView = view; @@ -118,19 +120,25 @@ public class DragPreviewProvider { return b; } + public final void generateDragOutline(Canvas canvas) { + if (ProviderConfig.IS_DOGFOOD_BUILD && gerenatedDragOutline != null) { + throw new RuntimeException("Drag outline generated twice"); + } + + gerenatedDragOutline = createDragOutline(canvas); + } + /** * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. * Responsibility for the bitmap is transferred to the caller. */ public Bitmap createDragOutline(Canvas canvas) { - final int outlineColor = mView.getResources().getColor(R.color.outline_color); final Bitmap b = Bitmap.createBitmap(mView.getWidth() + DRAG_BITMAP_PADDING, - mView.getHeight() + DRAG_BITMAP_PADDING, Bitmap.Config.ARGB_8888); - + mView.getHeight() + DRAG_BITMAP_PADDING, Bitmap.Config.ALPHA_8); canvas.setBitmap(b); drawDragView(canvas); HolographicOutlineHelper.obtain(mView.getContext()) - .applyExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor); + .applyExpensiveOutlineWithBlur(b, canvas); canvas.setBitmap(null); return b; } diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java index ddc9cbfd9..c86ba86fd 100644 --- a/src/com/android/launcher3/model/PackageItemInfo.java +++ b/src/com/android/launcher3/model/PackageItemInfo.java @@ -46,17 +46,12 @@ public class PackageItemInfo extends ItemInfo { */ public String titleSectionName; - int flags = 0; - PackageItemInfo(String packageName) { this.packageName = packageName; } @Override - public String toString() { - return "PackageItemInfo(title=" + title + " id=" + this.id - + " type=" + this.itemType + " container=" + this.container - + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY - + " spanX=" + spanX + " spanY=" + spanY + " user=" + user + ")"; + protected String dumpProperties() { + return super.dumpProperties() + " packageName=" + packageName; } } diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java index b3f0c8203..0d7ba1e11 100644 --- a/src/com/android/launcher3/model/WidgetItem.java +++ b/src/com/android/launcher3/model/WidgetItem.java @@ -68,6 +68,17 @@ public class WidgetItem extends ComponentKey implements Comparable<WidgetItem> { return thisWorkProfile ? 1 : -1; } - return sCollator.compare(label, another.label); + int labelCompare = sCollator.compare(label, another.label); + if (labelCompare != 0) { + return labelCompare; + } + + // If the label is same, put the smaller widget before the larger widget. If the area is + // also same, put the widget with smaller height before. + int thisArea = spanX * spanY; + int otherArea = another.spanX * another.spanY; + return thisArea == otherArea + ? Integer.compare(spanY, another.spanY) + : Integer.compare(thisArea, otherArea); } } diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java index 747c21bbe..fb9d2f7fe 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java +++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java @@ -226,11 +226,6 @@ public class PageIndicatorDots extends PageIndicator { public void setActiveMarker(int activePage) { if (mActivePage != activePage) { mActivePage = activePage; - - // Simulate a scroll change - int totalScroll = mNumPages - 1; - int currentScroll = mIsRtl ? (totalScroll - mActivePage) : mActivePage; - setScroll(currentScroll, totalScroll); } } diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java b/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java index ca3a2dd09..350bc8a9b 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java +++ b/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java @@ -159,6 +159,11 @@ public class PageIndicatorLineCaret extends PageIndicator { } @Override + public void setContentDescription(CharSequence contentDescription) { + mAllAppsHandle.setContentDescription(contentDescription); + } + + @Override public void setScroll(int currentScroll, int totalScroll) { if (getAlpha() == 0) { return; diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java index 9d8b6b3f4..47bee0669 100644 --- a/src/com/android/launcher3/provider/RestoreDbTask.java +++ b/src/com/android/launcher3/provider/RestoreDbTask.java @@ -24,6 +24,7 @@ import android.database.sqlite.SQLiteDatabase; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherProvider.DatabaseHelper; import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.logging.FileLog; @@ -43,6 +44,13 @@ public class RestoreDbTask { private static final String INFO_COLUMN_NAME = "name"; private static final String INFO_COLUMN_DEFAULT_VALUE = "dflt_value"; + /** + * When enabled all icons are kept on the home screen, even if they don't have an active + * session. To enable use: + * adb shell setprop log.tag.launcher_keep_all_icons VERBOSE + */ + private static final String KEEP_ALL_ICONS = "launcher_keep_all_icons"; + public static boolean performRestore(DatabaseHelper helper) { SQLiteDatabase db = helper.getWritableDatabase(); db.beginTransaction(); @@ -77,15 +85,17 @@ public class RestoreDbTask { } // Mark all items as restored. + boolean keepAllIcons = Utilities.isPropertyEnabled(KEEP_ALL_ICONS); ContentValues values = new ContentValues(); - values.put(Favorites.RESTORED, 1); + values.put(Favorites.RESTORED, ShortcutInfo.FLAG_RESTORED_ICON + | (keepAllIcons ? ShortcutInfo.FLAG_RESTORE_STARTED : 0)); db.update(Favorites.TABLE_NAME, values, null, null); // Mark widgets with appropriate restore flag - values.put(Favorites.RESTORED, - LauncherAppWidgetInfo.FLAG_ID_NOT_VALID | - LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY | - LauncherAppWidgetInfo.FLAG_UI_NOT_READY); + values.put(Favorites.RESTORED, LauncherAppWidgetInfo.FLAG_ID_NOT_VALID | + LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY | + LauncherAppWidgetInfo.FLAG_UI_NOT_READY | + (keepAllIcons ? LauncherAppWidgetInfo.FLAG_RESTORE_STARTED : 0)); db.update(Favorites.TABLE_NAME, values, "itemType = ?", new String[]{Integer.toString(Favorites.ITEM_TYPE_APPWIDGET)}); diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java index 37b6d0422..e7fc41512 100644 --- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java +++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java @@ -21,16 +21,19 @@ import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; import com.android.launcher3.IconCache; +import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LogAccelerateInterpolator; import com.android.launcher3.R; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; +import com.android.launcher3.shortcuts.DeepShortcutsContainer.UnbadgedShortcutInfo; import com.android.launcher3.util.PillRevealOutlineProvider; import com.android.launcher3.util.PillWidthRevealOutlineProvider; @@ -48,6 +51,8 @@ public class DeepShortcutView extends FrameLayout implements ValueAnimator.Anima private View mIconView; private float mOpenAnimationProgress; + private UnbadgedShortcutInfo mInfo; + public DeepShortcutView(Context context) { this(context, null, 0); } @@ -87,10 +92,36 @@ public class DeepShortcutView extends FrameLayout implements ValueAnimator.Anima mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); } - public void applyShortcutInfo(ShortcutInfo info) { + /** package private **/ + void applyShortcutInfo(UnbadgedShortcutInfo info, DeepShortcutsContainer container) { + mInfo = info; IconCache cache = LauncherAppState.getInstance().getIconCache(); mBubbleText.applyFromShortcutInfo(info, cache); mIconView.setBackground(mBubbleText.getIcon()); + + // Use the long label as long as it exists and fits. + CharSequence longLabel = info.mDetail.getLongLabel(); + int availableWidth = mBubbleText.getWidth() - mBubbleText.getTotalPaddingLeft() + - mBubbleText.getTotalPaddingRight(); + boolean usingLongLabel = !TextUtils.isEmpty(longLabel) + && mBubbleText.getPaint().measureText(longLabel.toString()) <= availableWidth; + mBubbleText.setText(usingLongLabel ? longLabel : info.mDetail.getShortLabel()); + + // TODO: Add the click handler to this view directly and not the child view. + mBubbleText.setOnClickListener(Launcher.getLauncher(getContext())); + mBubbleText.setOnLongClickListener(container); + mBubbleText.setOnTouchListener(container); + } + + /** + * Returns the shortcut info that is suitable to be added on the homescreen + */ + public ShortcutInfo getFinalInfo() { + ShortcutInfo badged = new ShortcutInfo(mInfo); + // Queue an update task on the worker thread. This ensures that the badged + // shortcut eventually gets its icon updated. + Launcher.getLauncher(getContext()).getModel().updateShortcutInfo(mInfo.mDetail, badged); + return badged; } public View getIconView() { diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java index 6bd21cd52..7657ed610 100644 --- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java +++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java @@ -31,7 +31,6 @@ import android.graphics.drawable.ShapeDrawable; import android.os.Build; import android.os.Handler; import android.os.Looper; -import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; import android.view.HapticFeedbackConstants; @@ -53,6 +52,7 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherViewPropertyAnimator; import com.android.launcher3.R; import com.android.launcher3.ShortcutInfo; @@ -62,9 +62,9 @@ import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.graphics.TriangleShape; -import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; @@ -182,12 +182,8 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC } for (int i = 0; i < shortcuts.size(); i++) { final ShortcutInfoCompat shortcut = shortcuts.get(i); - final ShortcutInfo launcherShortcutInfo = - new UnbadgedShortcutInfo(shortcut, mLauncher); - CharSequence shortLabel = shortcut.getShortLabel(); - CharSequence longLabel = shortcut.getLongLabel(); - uiHandler.post(new UpdateShortcutChild(i, launcherShortcutInfo, - shortLabel, longLabel)); + uiHandler.post(new UpdateShortcutChild( + i, new UnbadgedShortcutInfo(shortcut, mLauncher))); } } }); @@ -196,32 +192,17 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC /** Updates the child of this container at the given index based on the given shortcut info. */ private class UpdateShortcutChild implements Runnable { private int mShortcutChildIndex; - private ShortcutInfo mShortcutChildInfo; - private CharSequence mShortLabel; - private CharSequence mLongLabel; + private UnbadgedShortcutInfo mShortcutChildInfo; - public UpdateShortcutChild(int shortcutChildIndex, ShortcutInfo shortcutChildInfo, - CharSequence shortLabel, CharSequence longLabel) { + public UpdateShortcutChild(int shortcutChildIndex, UnbadgedShortcutInfo shortcutChildInfo) { mShortcutChildIndex = shortcutChildIndex; mShortcutChildInfo = shortcutChildInfo; - mShortLabel = shortLabel; - mLongLabel = longLabel; } @Override public void run() { - DeepShortcutView shortcutViewContainer = getShortcutAt(mShortcutChildIndex); - shortcutViewContainer.applyShortcutInfo(mShortcutChildInfo); - BubbleTextView shortcutView = getShortcutAt(mShortcutChildIndex).getBubbleText(); - // Use the long label as long as it exists and fits. - int availableWidth = shortcutView.getWidth() - shortcutView.getTotalPaddingLeft() - - shortcutView.getTotalPaddingRight(); - boolean usingLongLabel = !TextUtils.isEmpty(mLongLabel) - && shortcutView.getPaint().measureText(mLongLabel.toString()) <= availableWidth; - shortcutView.setText(usingLongLabel ? mLongLabel : mShortLabel); - shortcutView.setOnClickListener(mLauncher); - shortcutView.setOnLongClickListener(DeepShortcutsContainer.this); - shortcutView.setOnTouchListener(DeepShortcutsContainer.this); + getShortcutAt(mShortcutChildIndex) + .applyShortcutInfo(mShortcutChildInfo, DeepShortcutsContainer.this); } } @@ -273,8 +254,10 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC @Override public void onAnimationEnd(Animator animation) { mOpenCloseAnimator = null; - - sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + Utilities.sendCustomAccessibilityEvent( + DeepShortcutsContainer.this, + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + getContext().getString(R.string.action_deep_shortcut)); } }); @@ -524,14 +507,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC // Return if global dragging is not enabled if (!mLauncher.isDraggingEnabled()) return false; - UnbadgedShortcutInfo unbadgedInfo = (UnbadgedShortcutInfo) v.getTag(); - ShortcutInfo badged = new ShortcutInfo(unbadgedInfo); - // Queue an update task on the worker thread. This ensures that the badged - // shortcut eventually gets its icon updated. - mLauncher.getModel().updateShortcutInfo(unbadgedInfo.mDetail, badged); - // Long clicked on a shortcut. - mDeferContainerRemoval = true; DeepShortcutView sv = (DeepShortcutView) v.getParent(); sv.setWillDrawIcon(false); @@ -541,8 +517,8 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx; DragView dv = mLauncher.getWorkspace().beginDragShared( - sv.getBubbleText(), this, false, badged, - new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift)); + sv.getBubbleText(), this, sv.getFinalInfo(), + new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions()); dv.animateShift(-mIconShift.x, -mIconShift.y); // TODO: support dragging from within folder without having to close it @@ -586,7 +562,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC } @Override - public void onDragStart(DragSource source, ItemInfo info, int dragAction) { + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { // Either the original icon or one of the shortcuts was dragged. // Hide the container, but don't remove it yet because that interferes with touch events. animateClose(); @@ -603,8 +579,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC } else { // Close animation is not running. if (mDeferContainerRemoval) { - mDeferContainerRemoval = false; - mLauncher.getDragLayer().removeView(this); + close(); } } } @@ -625,7 +600,6 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC mOpenCloseAnimator.cancel(); } mIsOpen = false; - mLauncher.getDragController().removeDragListener(this); final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet(); final int shortcutCount = getShortcutCount(); @@ -714,7 +688,9 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC mDeferContainerRemoval = false; // Make the original icon visible in All Apps, but not in Workspace or Folders. cleanupDeferredDrag(mDeferredDragIcon.getTag() instanceof AppInfo); - mDeferredDragIcon.setTextVisibility(true); + boolean isInHotseat = ((ItemInfo) mDeferredDragIcon.getTag()).container + == LauncherSettings.Favorites.CONTAINER_HOTSEAT; + mDeferredDragIcon.setTextVisibility(!isInHotseat); mLauncher.getDragController().removeDragListener(this); mLauncher.getDragLayer().removeView(this); } @@ -753,8 +729,8 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC /** * Extension of {@link ShortcutInfo} which does not badge the icons. */ - private static class UnbadgedShortcutInfo extends ShortcutInfo { - private final ShortcutInfoCompat mDetail; + static class UnbadgedShortcutInfo extends ShortcutInfo { + public final ShortcutInfoCompat mDetail; public UnbadgedShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) { super(shortcutInfo, context); diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java index a25e475d4..2adb82e2d 100644 --- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java +++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java @@ -22,12 +22,9 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.View; -import android.widget.ImageView; -import com.android.launcher3.BubbleTextView; import com.android.launcher3.HolographicOutlineHelper; import com.android.launcher3.Launcher; -import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.graphics.DragPreviewProvider; @@ -45,23 +42,22 @@ public class ShortcutDragPreviewProvider extends DragPreviewProvider { @Override public Bitmap createDragOutline(Canvas canvas) { - Bitmap b = drawScaledPreview(canvas); + Bitmap b = drawScaledPreview(canvas, Bitmap.Config.ALPHA_8); - final int outlineColor = mView.getResources().getColor(R.color.outline_color); HolographicOutlineHelper.obtain(mView.getContext()) - .applyExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor); + .applyExpensiveOutlineWithBlur(b, canvas); canvas.setBitmap(null); return b; } @Override public Bitmap createDragBitmap(Canvas canvas) { - Bitmap b = drawScaledPreview(canvas); + Bitmap b = drawScaledPreview(canvas, Bitmap.Config.ARGB_8888); canvas.setBitmap(null); return b; } - private Bitmap drawScaledPreview(Canvas canvas) { + private Bitmap drawScaledPreview(Canvas canvas, Bitmap.Config config) { Drawable d = mView.getBackground(); Rect bounds = getDrawableBounds(d); @@ -70,7 +66,7 @@ public class ShortcutDragPreviewProvider extends DragPreviewProvider { final Bitmap b = Bitmap.createBitmap( size + DRAG_BITMAP_PADDING, size + DRAG_BITMAP_PADDING, - Bitmap.Config.ARGB_8888); + config); canvas.setBitmap(b); canvas.save(Canvas.MATRIX_SAVE_FLAG); diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java index ae552d284..6797c7ba3 100644 --- a/src/com/android/launcher3/testing/LauncherExtension.java +++ b/src/com/android/launcher3/testing/LauncherExtension.java @@ -139,13 +139,8 @@ public class LauncherExtension extends Launcher { } @Override - public boolean providesSearch() { - return false; - } - - @Override public boolean startSearch(String initialQuery, boolean selectInitialQuery, - Bundle appSearchData, Rect sourceBounds) { + Bundle appSearchData) { return false; } @@ -198,26 +193,6 @@ public class LauncherExtension extends Launcher { } @Override - public Intent getFirstRunActivity() { - return null; - } - - @Override - public boolean hasFirstRunActivity() { - return false; - } - - @Override - public boolean hasDismissableIntroScreen() { - return false; - } - - @Override - public View getIntroScreen() { - return null; - } - - @Override public boolean shouldMoveToDefaultScreenOnHomeIntent() { return true; } diff --git a/src/com/android/launcher3/util/ActivityResultInfo.java b/src/com/android/launcher3/util/ActivityResultInfo.java new file mode 100644 index 000000000..2261bee8c --- /dev/null +++ b/src/com/android/launcher3/util/ActivityResultInfo.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 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.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Utility class which stores information from onActivityResult + */ +public class ActivityResultInfo implements Parcelable { + + public final int requestCode; + public final int resultCode; + public final Intent data; + + public ActivityResultInfo(int requestCode, int resultCode, Intent data) { + this.requestCode = requestCode; + this.resultCode = resultCode; + this.data = data; + } + + private ActivityResultInfo(Parcel parcel) { + requestCode = parcel.readInt(); + resultCode = parcel.readInt(); + data = parcel.readInt() != 0 ? Intent.CREATOR.createFromParcel(parcel) : null; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(requestCode); + dest.writeInt(resultCode); + if (data != null) { + dest.writeInt(1); + data.writeToParcel(dest, flags); + } else { + dest.writeInt(0); + } + } + + public static final Parcelable.Creator<ActivityResultInfo> CREATOR = + new Parcelable.Creator<ActivityResultInfo>() { + public ActivityResultInfo createFromParcel(Parcel source) { + return new ActivityResultInfo(source); + } + + public ActivityResultInfo[] newArray(int size) { + return new ActivityResultInfo[size]; + } + }; +} diff --git a/src/com/android/launcher3/util/ComponentKey.java b/src/com/android/launcher3/util/ComponentKey.java index 144b411fa..5882f217d 100644 --- a/src/com/android/launcher3/util/ComponentKey.java +++ b/src/com/android/launcher3/util/ComponentKey.java @@ -32,8 +32,8 @@ public class ComponentKey { private final int mHashCode; public ComponentKey(ComponentName componentName, UserHandleCompat user) { - assert (componentName != null); - assert (user != null); + Preconditions.assertNotNull(componentName); + Preconditions.assertNotNull(user); this.componentName = componentName; this.user = user; mHashCode = Arrays.hashCode(new Object[] {componentName, user}); @@ -58,6 +58,8 @@ public class ComponentKey { componentName = ComponentName.unflattenFromString(componentKeyStr); user = UserHandleCompat.myUserHandle(); } + Preconditions.assertNotNull(componentName); + Preconditions.assertNotNull(user); mHashCode = Arrays.hashCode(new Object[] {componentName, user}); } diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java index 3c4c79aec..3e15d05e1 100644 --- a/src/com/android/launcher3/util/PackageManagerHelper.java +++ b/src/com/android/launcher3/util/PackageManagerHelper.java @@ -16,10 +16,15 @@ package com.android.launcher3.util; +import android.app.AppOpsManager; +import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; +import android.os.Build; +import android.text.TextUtils; import com.android.launcher3.Utilities; @@ -96,4 +101,53 @@ public class PackageManagerHelper { } return excludePackages.get(0); } + + /** + * Returns true if {@param srcPackage} has the permission required to start the activity from + * {@param intent}. If {@param srcPackage} is null, then the activity should not need + * any permissions + */ + public static boolean hasPermissionForActivity(Context context, Intent intent, + String srcPackage) { + PackageManager pm = context.getPackageManager(); + ResolveInfo target = pm.resolveActivity(intent, 0); + if (target == null) { + // Not a valid target + return false; + } + if (TextUtils.isEmpty(target.activityInfo.permission)) { + // No permission is needed + return true; + } + if (TextUtils.isEmpty(srcPackage)) { + // The activity requires some permission but there is no source. + return false; + } + + // Source does not have sufficient permissions. + if(pm.checkPermission(target.activityInfo.permission, srcPackage) != + PackageManager.PERMISSION_GRANTED) { + return false; + } + + if (!Utilities.ATLEAST_MARSHMALLOW) { + // These checks are sufficient for below M devices. + return true; + } + + // On M and above also check AppOpsManager for compatibility mode permissions. + if (TextUtils.isEmpty(AppOpsManager.permissionToOp(target.activityInfo.permission))) { + // There is no app-op for this permission, which could have been disabled. + return true; + } + + // There is no direct way to check if the app-op is allowed for a particular app. Since + // app-op is only enabled for apps running in compatibility mode, simply block such apps. + + try { + return pm.getApplicationInfo(srcPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M; + } catch (NameNotFoundException e) { } + + return false; + } } diff --git a/src/com/android/launcher3/util/PendingRequestArgs.java b/src/com/android/launcher3/util/PendingRequestArgs.java new file mode 100644 index 000000000..4e402f348 --- /dev/null +++ b/src/com/android/launcher3/util/PendingRequestArgs.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2016 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.content.ContentValues; +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppWidgetProviderInfo; + +/** + * Utility class to store information regarding a pending request made by launcher. This information + * can be saved across launcher instances. + */ +public class PendingRequestArgs extends ItemInfo implements Parcelable { + + private static final int TYPE_NONE = 0; + private static final int TYPE_INTENT = 1; + private static final int TYPE_APP_WIDGET = 2; + + private final int mArg1; + private final int mObjectType; + private final Parcelable mObject; + + public PendingRequestArgs(ItemInfo info) { + mArg1 = 0; + mObjectType = TYPE_NONE; + mObject = null; + + copyFrom(info); + } + + private PendingRequestArgs(int arg1, int objectType, Parcelable object) { + mArg1 = arg1; + mObjectType = objectType; + mObject = object; + } + + public PendingRequestArgs(Parcel parcel) { + readFromValues(ContentValues.CREATOR.createFromParcel(parcel)); + + mArg1 = parcel.readInt(); + mObjectType = parcel.readInt(); + if (parcel.readInt() != 0) { + mObject = mObjectType == TYPE_INTENT + ? Intent.CREATOR.createFromParcel(parcel) + : new LauncherAppWidgetProviderInfo(parcel); + } else { + mObject = null; + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + ContentValues itemValues = new ContentValues(); + writeToValues(itemValues); + + dest.writeInt(mArg1); + dest.writeInt(mObjectType); + if (mObject != null) { + dest.writeInt(1); + mObject.writeToParcel(dest, flags); + } else { + dest.writeInt(0); + } + } + + public LauncherAppWidgetProviderInfo getWidgetProvider() { + return mObjectType == TYPE_APP_WIDGET ? (LauncherAppWidgetProviderInfo) mObject : null; + } + + public int getWidgetId() { + return mObjectType == TYPE_APP_WIDGET ? mArg1 : 0; + } + + public Intent getPendingIntent() { + return mObjectType == TYPE_INTENT ? (Intent) mObject : null; + } + + public int getRequestCode() { + return mObjectType == TYPE_INTENT ? mArg1 : 0; + } + + public static PendingRequestArgs forWidgetInfo( + int appWidgetId, LauncherAppWidgetProviderInfo widgetInfo, ItemInfo info) { + PendingRequestArgs args = new PendingRequestArgs(appWidgetId, TYPE_APP_WIDGET, widgetInfo); + args.copyFrom(info); + return args; + } + + public static PendingRequestArgs forIntent(int requestCode, Intent intent, ItemInfo info) { + PendingRequestArgs args = new PendingRequestArgs(requestCode, TYPE_INTENT, intent); + args.copyFrom(info); + return args; + } + + public static final Parcelable.Creator<PendingRequestArgs> CREATOR = + new Parcelable.Creator<PendingRequestArgs>() { + public PendingRequestArgs createFromParcel(Parcel source) { + return new PendingRequestArgs(source); + } + + public PendingRequestArgs[] newArray(int size) { + return new PendingRequestArgs[size]; + } + }; +} diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java index 3760c6372..89353e110 100644 --- a/src/com/android/launcher3/util/Preconditions.java +++ b/src/com/android/launcher3/util/Preconditions.java @@ -26,6 +26,12 @@ import com.android.launcher3.config.ProviderConfig; */ public class Preconditions { + public static void assertNotNull(Object o) { + if (ProviderConfig.IS_DOGFOOD_BUILD && o == null) { + throw new IllegalStateException(); + } + } + public static void assertWorkerThread() { if (ProviderConfig.IS_DOGFOOD_BUILD && !isSameLooper(LauncherModel.getWorkerLooper())) { throw new IllegalStateException(); diff --git a/src/com/android/launcher3/util/VerticalFlingDetector.java b/src/com/android/launcher3/util/VerticalFlingDetector.java new file mode 100644 index 000000000..7236c2d1b --- /dev/null +++ b/src/com/android/launcher3/util/VerticalFlingDetector.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 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.content.Context; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; + +public class VerticalFlingDetector implements View.OnTouchListener { + + private static final float CUSTOM_SLOP_MULTIPLIER = 2.2f; + private static final int SEC_IN_MILLIS = 1000; + + private VelocityTracker mVelocityTracker; + private float mMinimumFlingVelocity; + private float mMaximumFlingVelocity; + private float mDownX, mDownY; + private boolean mShouldCheckFling; + private double mCustomTouchSlop; + + public VerticalFlingDetector(Context context) { + ViewConfiguration vc = ViewConfiguration.get(context); + mMinimumFlingVelocity = vc.getScaledMinimumFlingVelocity(); + mMaximumFlingVelocity = vc.getScaledMaximumFlingVelocity(); + mCustomTouchSlop = CUSTOM_SLOP_MULTIPLIER * vc.getScaledTouchSlop(); + } + + @Override + public boolean onTouch(View v, MotionEvent ev) { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + mDownX = ev.getX(); + mDownY = ev.getY(); + mShouldCheckFling = false; + break; + case MotionEvent.ACTION_MOVE: + if (mShouldCheckFling) { + break; + } + if (Math.abs(ev.getY() - mDownY) > mCustomTouchSlop && + Math.abs(ev.getY() - mDownY) > Math.abs(ev.getX() - mDownX)) { + mShouldCheckFling = true; + } + break; + case MotionEvent.ACTION_UP: + if (mShouldCheckFling) { + mVelocityTracker.computeCurrentVelocity(SEC_IN_MILLIS, mMaximumFlingVelocity); + // only when fling is detected in down direction + if (mVelocityTracker.getYVelocity() > mMinimumFlingVelocity) { + cleanUp(); + return true; + } + } + // fall through. + case MotionEvent.ACTION_CANCEL: + cleanUp(); + } + return false; + } + + private void cleanUp() { + if (mVelocityTracker == null) { + return; + } + mVelocityTracker.recycle(); + mVelocityTracker = null; + } +} diff --git a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java index a56985083..486b18ef2 100644 --- a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java +++ b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java @@ -35,10 +35,4 @@ public class PendingAddShortcutInfo extends PendingAddItemInfo { componentName = new ComponentName(activityInfo.packageName, activityInfo.name); itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; } - - @Override - public String toString() { - return String.format("PendingAddShortcutInfo package=%s, name=%s", - activityInfo.packageName, activityInfo.name); - } } diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java index de06ab664..f800ac44d 100644 --- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java +++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java @@ -59,10 +59,4 @@ public class PendingAddWidgetInfo extends PendingAddItemInfo { public boolean isCustomWidget() { return itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; } - - @Override - public String toString() { - return String.format("PendingAddWidgetInfo package=%s, name=%s", - componentName.getPackageName(), componentName.getShortClassName()); - } } diff --git a/src/com/android/launcher3/widget/PendingItemPreviewProvider.java b/src/com/android/launcher3/widget/PendingItemPreviewProvider.java new file mode 100644 index 000000000..eaa0bb3d5 --- /dev/null +++ b/src/com/android/launcher3/widget/PendingItemPreviewProvider.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 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.widget; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.view.View; + +import com.android.launcher3.HolographicOutlineHelper; +import com.android.launcher3.Launcher; +import com.android.launcher3.PendingAddItemInfo; +import com.android.launcher3.Workspace; +import com.android.launcher3.graphics.DragPreviewProvider; + +/** + * Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts + * dragged from the widget tray. + */ +public class PendingItemPreviewProvider extends DragPreviewProvider { + + private final PendingAddItemInfo mAddInfo; + private final Bitmap mPreviewBitmap; + + public PendingItemPreviewProvider(View view, PendingAddItemInfo addInfo, Bitmap preview) { + super(view); + mAddInfo = addInfo; + mPreviewBitmap = preview; + } + + @Override + public Bitmap createDragOutline(Canvas canvas) { + Workspace workspace = Launcher.getLauncher(mView.getContext()).getWorkspace(); + int[] size = workspace.estimateItemSize(mAddInfo, false); + + int w = size[0]; + int h = size[1]; + final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8); + canvas.setBitmap(b); + + Rect src = new Rect(0, 0, mPreviewBitmap.getWidth(), mPreviewBitmap.getHeight()); + float scaleFactor = Math.min((w - DRAG_BITMAP_PADDING) / (float) mPreviewBitmap.getWidth(), + (h - DRAG_BITMAP_PADDING) / (float) mPreviewBitmap.getHeight()); + int scaledWidth = (int) (scaleFactor * mPreviewBitmap.getWidth()); + int scaledHeight = (int) (scaleFactor * mPreviewBitmap.getHeight()); + Rect dst = new Rect(0, 0, scaledWidth, scaledHeight); + + // center the image + dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2); + + canvas.drawBitmap(mPreviewBitmap, src, dst, null); + + // Don't clip alpha values for the drag outline if we're using the default widget preview + boolean clipAlpha = !(mAddInfo instanceof PendingAddWidgetInfo && + (((PendingAddWidgetInfo) mAddInfo).previewImage == 0)); + HolographicOutlineHelper.obtain(mView.getContext()) + .applyExpensiveOutlineWithBlur(b, canvas, clipAlpha); + canvas.setBitmap(null); + + return b; + } +} diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index 97877fd35..293585dd6 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -135,6 +135,8 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { mWidgetName.setText(mItem.label); mWidgetDims.setText(getContext().getString(R.string.widget_dims_format, mItem.spanX, mItem.spanY)); + mWidgetDims.setContentDescription(getContext().getString( + R.string.widget_accessible_dims_format, mItem.spanX, mItem.spanY)); mWidgetPreviewLoader = loader; if (item.activityInfo != null) { diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java index 297505be2..049871f98 100644 --- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java +++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java @@ -11,6 +11,7 @@ import android.view.View; import com.android.launcher3.AppWidgetResizeFrame; import com.android.launcher3.DragSource; +import com.android.launcher3.DropTarget; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetProviderInfo; @@ -18,6 +19,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.util.Thunk; public class WidgetHostViewLoader implements DragController.DragListener { @@ -47,7 +49,7 @@ public class WidgetHostViewLoader implements DragController.DragListener { } @Override - public void onDragStart(DragSource source, ItemInfo info, int dragAction) { } + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { } @Override public void onDragEnd() { diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 538d4c988..89c44c859 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -33,6 +33,7 @@ import com.android.launcher3.CellLayout; import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.folder.Folder; import com.android.launcher3.IconCache; import com.android.launcher3.ItemInfo; @@ -48,6 +49,7 @@ import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; import com.android.launcher3.util.Thunk; +import com.android.launcher3.util.TransformingTouchDelegate; /** * The widgets list view container. @@ -63,11 +65,11 @@ public class WidgetsContainerView extends BaseContainerView private IconCache mIconCache; private final Rect mTmpBgPaddingRect = new Rect(); - private final Rect mTmpRect = new Rect(); /* Recycler view related member variables */ private WidgetsRecyclerView mRecyclerView; private WidgetsListAdapter mAdapter; + private TransformingTouchDelegate mRecyclerViewTouchDelegate; /* Touch handling related member variables. */ private Toast mWidgetInstructionToast; @@ -95,29 +97,29 @@ public class WidgetsContainerView extends BaseContainerView } @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + getRevealView().getBackground().getPadding(mTmpBgPaddingRect); + mRecyclerViewTouchDelegate.setBounds( + mRecyclerView.getLeft() - mTmpBgPaddingRect.left, + mRecyclerView.getTop() - mTmpBgPaddingRect.top, + mRecyclerView.getRight() + mTmpBgPaddingRect.right, + mRecyclerView.getBottom() + mTmpBgPaddingRect.bottom); + } + + @Override protected void onFinishInflate() { super.onFinishInflate(); mRecyclerView = (WidgetsRecyclerView) getContentView().findViewById(R.id.widgets_list_view); mRecyclerView.setAdapter(mAdapter); mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + mRecyclerViewTouchDelegate = new TransformingTouchDelegate(mRecyclerView); } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - getRevealView().getBackground().getPadding(mTmpBgPaddingRect); - if (Utilities.isRtl(getResources())) { - getContentView().setPadding(0, mTmpBgPaddingRect.top, mTmpBgPaddingRect.right, - mTmpBgPaddingRect.bottom); - mTmpRect.set(mTmpBgPaddingRect.left, 0, 0, 0); - mRecyclerView.updateBackgroundPadding(mTmpRect); - } else { - getContentView().setPadding(mTmpBgPaddingRect.left, mTmpBgPaddingRect.top, 0, - mTmpBgPaddingRect.bottom); - mTmpRect.set(0, 0, mTmpBgPaddingRect.right, 0); - mRecyclerView.updateBackgroundPadding(mTmpRect); - } - - super.onMeasure(widthMeasureSpec, heightMeasureSpec); + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + ((View) mRecyclerView.getParent()).setTouchDelegate(mRecyclerViewTouchDelegate); } // @@ -207,7 +209,7 @@ public class WidgetsContainerView extends BaseContainerView // Compose the drag image Bitmap preview; - float scale = 1f; + final float scale; final Rect bounds = image.getBitmapBounds(); if (createItemInfo instanceof PendingAddWidgetInfo) { @@ -244,19 +246,14 @@ public class WidgetsContainerView extends BaseContainerView scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / preview.getWidth(); } - // Don't clip alpha values for the drag outline if we're using the default widget preview - boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo && - (((PendingAddWidgetInfo) createItemInfo).previewImage == 0)); + // Since we are not going through the workspace for starting the drag, set drag related + // information on the workspace before starting the drag. + mLauncher.getWorkspace().prepareDragWithProvider( + new PendingItemPreviewProvider(v, createItemInfo, preview)); // Start the drag - mLauncher.lockScreenOrientation(); mDragController.startDrag(image, preview, this, createItemInfo, - bounds, DragController.DRAG_ACTION_COPY, scale); - // This call expects the extra empty screen to already be created, which is why we call it - // after mDragController.startDrag(). - mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, preview, clipAlpha); - - preview.recycle(); + bounds, scale, new DragOptions()); return true; } |