diff options
59 files changed, 1479 insertions, 1480 deletions
diff --git a/build.gradle b/build.gradle index e103d792f..0c00da9d3 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.3' + classpath 'com.android.tools.build:gradle:2.2.0' classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0' } } diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml index a2e2f9bba..dd981dd20 100644 --- a/res/layout-port/launcher.xml +++ b/res/layout-port/launcher.xml @@ -77,8 +77,7 @@ android:id="@+id/apps_view" android:layout_width="match_parent" android:layout_height="match_parent" - android:visibility="invisible" - launcher:layout_ignoreInsets="true" /> + android:visibility="invisible" /> </com.android.launcher3.dragndrop.DragLayer> </com.android.launcher3.LauncherRootView> diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml index 12c01b77d..06cb55040 100644 --- a/res/layout-sw720dp/launcher.xml +++ b/res/layout-sw720dp/launcher.xml @@ -76,8 +76,7 @@ android:id="@+id/apps_view" android:layout_width="match_parent" android:layout_height="match_parent" - android:visibility="invisible" - launcher:layout_ignoreInsets="true" /> + android:visibility="invisible" /> </com.android.launcher3.dragndrop.DragLayer> </com.android.launcher3.LauncherRootView> diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml index 4909eb38b..926883927 100644 --- a/res/layout/all_apps.xml +++ b/res/layout/all_apps.xml @@ -46,10 +46,10 @@ <!-- DO NOT CHANGE THE ID --> <com.android.launcher3.allapps.AllAppsRecyclerView android:id="@+id/apps_list_view" + android:layout_below="@+id/search_container" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center_horizontal|top" - android:layout_marginTop="@dimen/all_apps_search_bar_height" android:clipToPadding="false" android:descendantFocusability="afterDescendants" android:focusable="true" @@ -60,7 +60,6 @@ android:layout_width="match_parent" android:layout_height="@dimen/all_apps_search_bar_height" android:layout_gravity="center|top" - android:paddingTop="@dimen/all_apps_search_bar_margin_top" android:gravity="center|bottom" android:orientation="horizontal" android:saveEnabled="false"> @@ -68,8 +67,9 @@ <com.android.launcher3.ExtendedEditText android:id="@+id/search_box_input" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="@dimen/all_apps_search_bar_field_height" android:background="@android:color/transparent" + android:layout_gravity="bottom" android:focusableInTouchMode="true" android:gravity="center" android:imeOptions="actionSearch|flagNoExtractUi" diff --git a/res/values/dimens.xml b/res/values/dimens.xml index c112949ba..12e902ac3 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -64,8 +64,8 @@ <dimen name="all_apps_grid_view_start_margin">0dp</dimen> <dimen name="all_apps_grid_section_y_offset">8dp</dimen> <dimen name="all_apps_grid_section_text_size">24sp</dimen> + <dimen name="all_apps_search_bar_field_height">48dp</dimen> <dimen name="all_apps_search_bar_height">60dp</dimen> - <dimen name="all_apps_search_bar_margin_top">12dp</dimen> <dimen name="all_apps_search_bar_icon_margin_right">4dp</dimen> <dimen name="all_apps_search_bar_icon_margin_top">1dp</dimen> <dimen name="all_apps_list_bottom_padding">8dp</dimen> @@ -164,7 +164,7 @@ <dimen name="bg_pill_height">48dp</dimen> <dimen name="bg_pill_radius">24dp</dimen> <dimen name="deep_shortcuts_spacing">4dp</dimen> - <dimen name="deferred_drag_view_scale">6dp</dimen> + <dimen name="pre_drag_view_scale">6dp</dimen> <!-- an icon with shortcuts must be dragged this far before the container is removed. --> <dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen> <dimen name="deep_shortcut_icon_size">36dp</dimen> @@ -182,6 +182,9 @@ also happens to equal 19dp--> <dimen name="deep_shortcuts_arrow_horizontal_offset">19dp</dimen> +<!-- Touch handling --> + <dimen name="edge_of_screen_threshold">8dp</dimen> + <!-- Other --> <!-- Approximates the system status bar height. Not guaranteed to be always be correct. --> <dimen name="status_bar_height">24dp</dimen> diff --git a/res/values/strings.xml b/res/values/strings.xml index 60a37e5ef..a9c970d6d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -37,6 +37,10 @@ <string name="safemode_widget_error">Widgets disabled in Safe mode</string> <!-- Message shown when a shortcut is not available. It could have been temporarily disabled and may start working again after some time. --> <string name="shortcut_not_available">Shortcut isn\'t available</string> + <!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] --> + <string name="home_screen">Home screen</string> + <!-- Label for showing custom action list of a shortcut or widget. [CHAR_LIMIT=30] --> + <string name="custom_actions">Custom actions</string> <!-- Widgets --> <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] --> diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index c4315936c..c427ddc93 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -23,7 +23,7 @@ import com.android.launcher3.compat.LauncherActivityInfoCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.util.FlagOp; -import com.android.launcher3.util.StringFilter; +import com.android.launcher3.util.ItemInfoMatcher; import java.util.ArrayList; import java.util.HashSet; @@ -121,14 +121,13 @@ class AllAppsList { } /** - * Updates the apps for the given packageName and user based on {@param op}. + * Updates the disabled flags of apps matching {@param matcher} based on {@param op}. */ - public void updatePackageFlags(StringFilter pkgFilter, UserHandleCompat user, FlagOp op) { + public void updateDisabledFlags(ItemInfoMatcher matcher, FlagOp op) { final List<AppInfo> data = this.data; for (int i = data.size() - 1; i >= 0; i--) { AppInfo info = data.get(i); - final ComponentName component = info.intent.getComponent(); - if (info.user.equals(user) && pkgFilter.matches(component.getPackageName())) { + if (matcher.matches(info, info.intent.getComponent())) { info.isDisabled = op.apply(info.isDisabled); modified.add(info); } diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index daeca3bef..f0ec50343 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -15,14 +15,17 @@ import android.graphics.Point; import android.graphics.Rect; import android.view.Gravity; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import com.android.launcher3.accessibility.DragViewStateAnnouncer; import com.android.launcher3.util.FocusLogic; +import com.android.launcher3.util.TouchController; -public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListener { +public class AppWidgetResizeFrame extends FrameLayout + implements View.OnKeyListener, TouchController { private static final int SNAP_DURATION = 150; private static final float DIMMED_HANDLE_ALPHA = 0f; private static final float RESIZE_THRESHOLD = 0.66f; @@ -76,6 +79,8 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe private int mTopTouchRegionAdjustment = 0; private int mBottomTouchRegionAdjustment = 0; + private int mXDown, mYDown; + public AppWidgetResizeFrame(Context context, LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) { @@ -205,7 +210,7 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe } } - public void visualizeResizeForDelta(int deltaX, int deltaY) { + private void visualizeResizeForDelta(int deltaX, int deltaY) { visualizeResizeForDelta(deltaX, deltaY, false); } @@ -398,7 +403,7 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe requestLayout(); } - public void onTouchUp() { + private void onTouchUp() { int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap(); int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap(); @@ -493,10 +498,56 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe public boolean onKey(View v, int keyCode, KeyEvent event) { // Clear the frame and give focus to the widget host view when a directional key is pressed. if (FocusLogic.shouldConsume(keyCode)) { - mDragLayer.clearAllResizeFrames(); + mDragLayer.clearResizeFrame(); mWidgetView.requestFocus(); return true; } return false; } + + private boolean handleTouchDown(MotionEvent ev) { + Rect hitRect = new Rect(); + int x = (int) ev.getX(); + int y = (int) ev.getY(); + + getHitRect(hitRect); + if (hitRect.contains(x, y)) { + if (beginResizeIfPointInRegion(x - getLeft(), y - getTop())) { + mXDown = x; + mYDown = y; + return true; + } + } + return false; + } + + @Override + public boolean onControllerTouchEvent(MotionEvent ev) { + int action = ev.getAction(); + int x = (int) ev.getX(); + int y = (int) ev.getY(); + + switch (action) { + case MotionEvent.ACTION_DOWN: + return handleTouchDown(ev); + case MotionEvent.ACTION_MOVE: + visualizeResizeForDelta(x - mXDown, y - mYDown); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + visualizeResizeForDelta(x - mXDown, y - mYDown); + onTouchUp(); + mXDown = mYDown = 0; + break; + } + return true; + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) { + return true; + } + return false; + } } diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index d5309b4f0..8b5a8a863 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -38,6 +38,7 @@ import android.util.Patterns; import com.android.launcher3.LauncherProvider.SqlArguments; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.util.Thunk; import org.xmlpull.v1.XmlPullParser; @@ -436,7 +437,7 @@ public class AutoInstallsLayout { return -1; } - ItemInfo.writeBitmap(mValues, Utilities.createIconBitmap(icon, mContext)); + ItemInfo.writeBitmap(mValues, LauncherIcons.createIconBitmap(icon, mContext)); mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId)); mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId)); diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java index 96942ee6f..b7321d94f 100644 --- a/src/com/android/launcher3/BaseContainerView.java +++ b/src/com/android/launcher3/BaseContainerView.java @@ -16,17 +16,24 @@ package com.android.launcher3; +import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; +import android.view.ViewConfiguration; import android.widget.FrameLayout; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.TransformingTouchDelegate; /** * A base container view, which supports resizing. @@ -39,12 +46,16 @@ public abstract class BaseContainerView extends FrameLayout protected int mContainerPaddingTop; protected int mContainerPaddingBottom; - private InsetDrawable mRevealDrawable; protected final Drawable mBaseDrawable; + private final Rect mBgPaddingRect = new Rect(); private View mRevealView; private View mContent; + private TransformingTouchDelegate mTouchDelegate; + + private final PointF mLastTouchDownPosPx = new PointF(-1.0f, -1.0f); + public BaseContainerView(Context context) { this(context, null); } @@ -72,6 +83,12 @@ public abstract class BaseContainerView extends FrameLayout DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); grid.addLauncherLayoutChangedListener(this); + + View touchDelegateTargetView = getTouchDelegateTargetView(); + if (touchDelegateTargetView != null) { + mTouchDelegate = new TransformingTouchDelegate(touchDelegateTargetView); + ((View) touchDelegateTargetView.getParent()).setTouchDelegate(mTouchDelegate); + } } @Override @@ -93,10 +110,36 @@ public abstract class BaseContainerView extends FrameLayout } @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + getRevealView().getBackground().getPadding(mBgPaddingRect); + + View touchDelegateTargetView = getTouchDelegateTargetView(); + if (touchDelegateTargetView != null) { + mTouchDelegate.setBounds( + touchDelegateTargetView.getLeft() - mBgPaddingRect.left, + touchDelegateTargetView.getTop() - mBgPaddingRect.top, + touchDelegateTargetView.getRight() + mBgPaddingRect.right, + touchDelegateTargetView.getBottom() + mBgPaddingRect.bottom); + } + } + + @Override public void onLauncherLayoutChanged() { updatePaddings(); } + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return handleTouchEvent(ev); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent ev) { + return handleTouchEvent(ev); + } + public void setRevealDrawableColor(int color) { ((ColorDrawable) mBaseDrawable).setColor(color); } @@ -130,14 +173,47 @@ public abstract class BaseContainerView extends FrameLayout } } - mRevealDrawable = new InsetDrawable(mBaseDrawable, + InsetDrawable revealDrawable = new InsetDrawable(mBaseDrawable, mContainerPaddingLeft, mContainerPaddingTop, mContainerPaddingRight, mContainerPaddingBottom); - mRevealView.setBackground(mRevealDrawable); - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && this instanceof AllAppsContainerView) { - // Skip updating the content background - } else { - mContent.setBackground(mRevealDrawable); + mRevealView.setBackground(revealDrawable); + mContent.setBackground(revealDrawable); + } + + /** + * Handles the touch events that shows the workspace when clicking outside the bounds of the + * touch delegate target view. + */ + private boolean handleTouchEvent(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + // Check if the touch is outside touch delegate target view + View touchDelegateTargetView = getTouchDelegateTargetView(); + float leftBoundPx = touchDelegateTargetView.getLeft(); + if (ev.getX() < leftBoundPx || + ev.getX() > (touchDelegateTargetView.getWidth() + leftBoundPx)) { + mLastTouchDownPosPx.set((int) ev.getX(), (int) ev.getY()); + } + break; + case MotionEvent.ACTION_UP: + if (mLastTouchDownPosPx.x > -1) { + ViewConfiguration viewConfig = ViewConfiguration.get(getContext()); + float dx = ev.getX() - mLastTouchDownPosPx.x; + float dy = ev.getY() - mLastTouchDownPosPx.y; + float distance = PointF.length(dx, dy); + if (distance < viewConfig.getScaledTouchSlop()) { + // The background was clicked, so just go home + Launcher.getLauncher(getContext()).showWorkspace(true); + return true; + } + } + // Fall through + case MotionEvent.ACTION_CANCEL: + mLastTouchDownPosPx.set(-1, -1); + break; } + return false; } + + public abstract View getTouchDelegateTargetView(); } diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java index b9e627775..b9b044db9 100644 --- a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java +++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java @@ -25,6 +25,8 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import com.android.launcher3.graphics.HolographicOutlineHelper; + /** * The fast scroller popup that shows the section name the list will jump to. */ @@ -116,7 +118,7 @@ public class BaseRecyclerViewFastScrollPopup { mBgBounds.bottom = mBgBounds.top + bgHeight; // Generate a bitmap for a shadow matching these bounds - mShadow = HolographicOutlineHelper.obtain( + mShadow = HolographicOutlineHelper.getInstance( mRv.getContext()).createMediumDropShadow(mBg, false /* shouldCache */); } else { mShadow = null; diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index a294fa538..7d693ec2b 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -42,6 +42,7 @@ import android.widget.TextView; import com.android.launcher3.IconCache.IconLoadRequest; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.graphics.HolographicOutlineHelper; import com.android.launcher3.model.PackageItemInfo; import java.text.NumberFormat; @@ -150,7 +151,7 @@ public class BubbleTextView extends TextView mLongPressHelper = new CheckLongPressHelper(this); mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); - mOutlineHelper = HolographicOutlineHelper.obtain(getContext()); + mOutlineHelper = HolographicOutlineHelper.getInstance(getContext()); setAccessibilityDelegate(mLauncher.getAccessibilityDelegate()); } @@ -328,7 +329,7 @@ public class BubbleTextView extends TextView void setStayPressed(boolean stayPressed) { mStayPressed = stayPressed; if (!stayPressed) { - HolographicOutlineHelper.obtain(getContext()).recycleShadowBitmap(mPressedBackground); + HolographicOutlineHelper.getInstance(getContext()).recycleShadowBitmap(mPressedBackground); mPressedBackground = null; } else { if (mPressedBackground == null) { diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index c4e6ed119..8e4567bd8 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -104,7 +104,7 @@ public class DeviceProfile { public int hotseatCellWidthPx; public int hotseatCellHeightPx; public int hotseatIconSizePx; - private int hotseatBarHeightPx; + public int hotseatBarHeightPx; private int hotseatBarTopPaddingPx; private int hotseatLandGutterPx; diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java index c73ceea14..de079feda 100644 --- a/src/com/android/launcher3/FocusHelper.java +++ b/src/com/android/launcher3/FocusHelper.java @@ -250,14 +250,6 @@ public class FocusHelper { } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && profile.isVerticalBarLayout()) { keyCode = KeyEvent.KEYCODE_PAGE_DOWN; - } else if (isUninstallKeyChord(e)) { - matrix = FocusLogic.createSparseMatrix(iconLayout); - if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) { - UninstallDropTarget.startUninstallActivity(launcher, itemInfo); - } - } else if (isDeleteKeyChord(e)) { - matrix = FocusLogic.createSparseMatrix(iconLayout); - launcher.removeItem(v, itemInfo, true /* deleteFromDb */); } else { // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the // matrix extended with hotseat. @@ -374,14 +366,6 @@ public class FocusHelper { } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && profile.isVerticalBarLayout()) { matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile); - } else if (isUninstallKeyChord(e)) { - matrix = FocusLogic.createSparseMatrix(iconLayout); - if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) { - UninstallDropTarget.startUninstallActivity(launcher, itemInfo); - } - } else if (isDeleteKeyChord(e)) { - matrix = FocusLogic.createSparseMatrix(iconLayout); - launcher.removeItem(v, itemInfo, true /* deleteFromDb */); } else { matrix = FocusLogic.createSparseMatrix(iconLayout); } @@ -532,24 +516,6 @@ public class FocusHelper { } } - /** - * Returns whether the key event represents a valid uninstall key chord. - */ - private static boolean isUninstallKeyChord(KeyEvent event) { - int keyCode = event.getKeyCode(); - return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) && - event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON); - } - - /** - * Returns whether the key event represents a valid delete key chord. - */ - private static boolean isDeleteKeyChord(KeyEvent event) { - int keyCode = event.getKeyCode(); - return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) && - event.hasModifiers(KeyEvent.META_CTRL_ON); - } - private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout, int pageIndex, boolean isRtl) { if (pageIndex - 1 < 0) { diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index d3fb38ede..8edfdf593 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -47,6 +47,7 @@ import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.SQLiteCacheHelper; @@ -187,7 +188,7 @@ public class IconCache { private Bitmap makeDefaultIcon(UserHandleCompat user) { Drawable unbadged = getFullResDefaultActivityIcon(); - return Utilities.createBadgedIconBitmap(unbadged, user, mContext); + return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext); } /** @@ -387,7 +388,7 @@ public class IconCache { } if (entry == null) { entry = new CacheEntry(); - entry.icon = Utilities.createBadgedIconBitmap( + entry.icon = LauncherIcons.createBadgedIconBitmap( mIconProvider.getIcon(app, mIconDpi), app.getUser(), mContext); } @@ -555,7 +556,7 @@ public class IconCache { // Check the DB first. if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) { if (info != null) { - entry.icon = Utilities.createBadgedIconBitmap( + entry.icon = LauncherIcons.createBadgedIconBitmap( mIconProvider.getIcon(info, mIconDpi), info.getUser(), mContext); } else { @@ -606,7 +607,7 @@ public class IconCache { entry.title = title; } if (icon != null) { - entry.icon = Utilities.createIconBitmap(icon, mContext); + entry.icon = LauncherIcons.createIconBitmap(icon, mContext); } } @@ -641,7 +642,7 @@ public class IconCache { // Load the full res icon for the application, but if useLowResIcon is set, then // only keep the low resolution icon instead of the larger full-sized icon - Bitmap icon = Utilities.createBadgedIconBitmap( + Bitmap icon = LauncherIcons.createBadgedIconBitmap( appInfo.loadIcon(mPackageManager), user, mContext); Bitmap lowResIcon = generateLowResIcon(icon, mPackageBgColor); entry.title = appInfo.loadLabel(mPackageManager); diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index c73a7a61d..30d5b17c5 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -30,7 +30,6 @@ import android.app.AlertDialog; import android.app.SearchManager; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProviderInfo; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks2; @@ -56,7 +55,6 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.os.Message; import android.os.StrictMode; import android.os.SystemClock; import android.os.Trace; @@ -69,6 +67,8 @@ import android.util.Log; import android.view.Display; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; +import android.view.KeyboardShortcutGroup; +import android.view.KeyboardShortcutInfo; import android.view.Menu; import android.view.MotionEvent; import android.view.Surface; @@ -79,9 +79,9 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.OvershootInterpolator; import android.view.inputmethod.InputMethodManager; -import android.widget.Advanceable; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; @@ -106,6 +106,7 @@ import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dynamicui.ExtractedColors; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.keyboard.CustomActionsPopup; import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.logging.FileLog; import com.android.launcher3.logging.UserEventDispatcher; @@ -254,7 +255,6 @@ public class Launcher extends Activity @Thunk WidgetsContainerView mWidgetsView; @Thunk WidgetsModel mWidgetsModel; - private Bundle mSavedState; // We set the state in both onCreate and then onNewIntent in some cases, which causes both // scroll issues (because the workspace may not have been measured yet) and extra work. // Instead, just save the state that we need to restore Launcher to, and commit it in onResume. @@ -275,27 +275,16 @@ public class Launcher extends Activity private IconCache mIconCache; private ExtractedColors mExtractedColors; private LauncherAccessibilityDelegate mAccessibilityDelegate; + private Handler mHandler = new Handler(); private boolean mIsResumeFromActionScreenOff; - @Thunk boolean mUserPresent = true; - private boolean mVisible; - private boolean mHasFocus; - private boolean mAttached; + private boolean mHasFocus = false; + private boolean mAttached = false; /** Maps launcher activity components to their list of shortcut ids. */ private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>(); private View.OnTouchListener mHapticFeedbackTouchListener; - // Related to the auto-advancing of widgets - private final int ADVANCE_MSG = 1; - private static final int ADVANCE_INTERVAL = 20000; - private static final int ADVANCE_STAGGER = 250; - - private boolean mAutoAdvanceRunning = false; - private long mAutoAdvanceSentTime; - private long mAutoAdvanceTimeLeft = -1; - @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = new HashMap<>(); - // Determines how long to wait after a rotation before restoring the screen orientation to // match the sensor state. private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500; @@ -354,6 +343,9 @@ public class Launcher extends Activity private UserEventDispatcher mUserEventDispatcher; + private float mLastDispatchTouchEventX = 0.0f; + private float mEdgeOfScreenThresholdPx = 0.0f; + public ViewGroupFocusHelper mFocusHandler; private boolean mRotationEnabled = false; @@ -424,6 +416,9 @@ public class Launcher extends Activity setContentView(R.layout.launcher); + mEdgeOfScreenThresholdPx = getResources() + .getDimensionPixelSize(R.dimen.edge_of_screen_threshold); + setupViews(); mDeviceProfile.layout(this, false /* notifyListeners */); mExtractedColors = new ExtractedColors(); @@ -434,8 +429,7 @@ public class Launcher extends Activity lockAllApps(); - mSavedState = savedInstanceState; - restoreState(mSavedState); + restoreState(savedInstanceState); if (LauncherAppState.PROFILE_STARTUP) { Trace.endSection(); @@ -443,11 +437,18 @@ public class Launcher extends Activity // 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())) { + int currentScreen = PagedView.INVALID_RESTORE_PAGE; + if (savedInstanceState != null) { + currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen); + } + if (!mModel.startLoader(currentScreen)) { // If we are not binding synchronously, show a fade in animation when // the first page bind completes. mDragLayer.setAlpha(0); } else { + // Pages bound synchronously. + mWorkspace.setCurrentPage(currentScreen); + setWorkspaceLoading(true); } @@ -611,7 +612,7 @@ public class Launcher extends Activity } /** - * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to + * Invoked by subclasses to signal a change to the {@link #addToCustomContentPage} value to * ensure the custom content page is added or removed if necessary. */ protected void invalidateHasCustomContentToLeft() { @@ -1263,22 +1264,6 @@ public class Launcher extends Activity } /** - * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type - * State - */ - private static State intToState(int stateOrdinal) { - State state = State.WORKSPACE; - final State[] stateValues = State.values(); - for (int i = 0; i < stateValues.length; i++) { - if (stateValues[i].ordinal() == stateOrdinal) { - state = stateValues[i]; - break; - } - } - return state; - } - - /** * Restores the previous state, if it exists. * * @param savedState The previous state. @@ -1288,17 +1273,14 @@ public class Launcher extends Activity return; } - State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal())); + int stateOrdinal = savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()); + State[] stateValues = State.values(); + State state = (stateOrdinal >= 0 && stateOrdinal < stateValues.length) + ? stateValues[stateOrdinal] : State.WORKSPACE; if (state == State.APPS || state == State.WIDGETS) { mOnResumeState = state; } - int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, - PagedView.INVALID_RESTORE_PAGE); - if (currentScreen != PagedView.INVALID_RESTORE_PAGE) { - mWorkspace.setRestorePage(currentScreen); - } - PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS); if (requestArgs != null) { setWaitingForResult(requestArgs); @@ -1576,10 +1558,6 @@ public class Launcher extends Activity mWorkspace.addInScreen(hostView, item.container, item.screenId, item.cellX, item.cellY, item.spanX, item.spanY, insert); - - if (!item.isCustomWidget()) { - addWidgetToAutoAdvanceIfNeeded(hostView, appWidgetInfo); - } } private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -1587,9 +1565,7 @@ public class Launcher extends Activity public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (Intent.ACTION_SCREEN_OFF.equals(action)) { - mUserPresent = false; - mDragLayer.clearAllResizeFrames(); - updateAutoAdvanceState(); + mDragLayer.clearResizeFrame(); // Reset AllApps to its initial state only if we are not in the middle of // processing a multi-step drop @@ -1600,9 +1576,6 @@ public class Launcher extends Activity } } mIsResumeFromActionScreenOff = true; - } else if (Intent.ACTION_USER_PRESENT.equals(action)) { - mUserPresent = true; - updateAutoAdvanceState(); } } }; @@ -1614,11 +1587,9 @@ public class Launcher extends Activity // Listen for broadcasts related to user-presence final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_USER_PRESENT); registerReceiver(mReceiver, filter); FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView()); mAttached = true; - mVisible = true; if (mLauncherCallbacks != null) { mLauncherCallbacks.onAttachedToWindow(); @@ -1628,13 +1599,10 @@ public class Launcher extends Activity @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); - mVisible = false; - if (mAttached) { unregisterReceiver(mReceiver); mAttached = false; } - updateAutoAdvanceState(); if (mLauncherCallbacks != null) { mLauncherCallbacks.onDetachedFromWindow(); @@ -1642,12 +1610,10 @@ public class Launcher extends Activity } public void onWindowVisibilityChanged(int visibility) { - mVisible = visibility == View.VISIBLE; - updateAutoAdvanceState(); // The following code used to be in onResume, but it turns out onResume is called when // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged // is a more appropriate event to handle - if (mVisible) { + if (visibility == View.VISIBLE) { if (!mWorkspaceLoading) { final ViewTreeObserver observer = mWorkspace.getViewTreeObserver(); // We want to let Launcher draw itself at least once before we force it to build @@ -1682,72 +1648,6 @@ public class Launcher extends Activity } } - @Thunk void sendAdvanceMessage(long delay) { - mHandler.removeMessages(ADVANCE_MSG); - Message msg = mHandler.obtainMessage(ADVANCE_MSG); - mHandler.sendMessageDelayed(msg, delay); - mAutoAdvanceSentTime = System.currentTimeMillis(); - } - - @Thunk void updateAutoAdvanceState() { - boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty(); - if (autoAdvanceRunning != mAutoAdvanceRunning) { - mAutoAdvanceRunning = autoAdvanceRunning; - if (autoAdvanceRunning) { - long delay = mAutoAdvanceTimeLeft == -1 ? ADVANCE_INTERVAL : mAutoAdvanceTimeLeft; - sendAdvanceMessage(delay); - } else { - if (!mWidgetsToAdvance.isEmpty()) { - mAutoAdvanceTimeLeft = Math.max(0, ADVANCE_INTERVAL - - (System.currentTimeMillis() - mAutoAdvanceSentTime)); - } - mHandler.removeMessages(ADVANCE_MSG); - mHandler.removeMessages(0); // Remove messages sent using postDelayed() - } - } - } - - @Thunk final Handler mHandler = new Handler(new Handler.Callback() { - - @Override - public boolean handleMessage(Message msg) { - if (msg.what == ADVANCE_MSG) { - int i = 0; - for (View key: mWidgetsToAdvance.keySet()) { - final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId); - final int delay = ADVANCE_STAGGER * i; - if (v instanceof Advanceable) { - mHandler.postDelayed(new Runnable() { - public void run() { - ((Advanceable) v).advance(); - } - }, delay); - } - i++; - } - sendAdvanceMessage(ADVANCE_INTERVAL); - } - return true; - } - }); - - private void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) { - if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return; - View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId); - if (v instanceof Advanceable) { - mWidgetsToAdvance.put(hostView, appWidgetInfo); - ((Advanceable) v).fyiWillBeAdvancedByHostKThx(); - updateAutoAdvanceState(); - } - } - - private void removeWidgetToAutoAdvance(View hostView) { - if (mWidgetsToAdvance.containsKey(hostView)) { - mWidgetsToAdvance.remove(hostView); - updateAutoAdvanceState(); - } - } - public void showOutOfSpaceMessage(boolean isHotseatLayout) { int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space); Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show(); @@ -1942,9 +1842,6 @@ public class Launcher extends Activity public void onDestroy() { super.onDestroy(); - // Remove all pending runnables - mHandler.removeMessages(ADVANCE_MSG); - mHandler.removeMessages(0); mWorkspace.removeCallbacks(mBuildLayersRunnable); mWorkspace.removeFolderListeners(); @@ -1967,8 +1864,6 @@ public class Launcher extends Activity } mAppWidgetHost = null; - mWidgetsToAdvance.clear(); - TextKeyListener.getInstance().release(); ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE)) @@ -2288,7 +2183,6 @@ public class Launcher extends Activity } else if (itemInfo instanceof LauncherAppWidgetInfo) { final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo; mWorkspace.removeWorkspaceItem(v); - removeWidgetToAutoAdvance(v); if (deleteFromDb) { deleteWidgetInfo(widgetInfo); } @@ -2781,6 +2675,7 @@ public class Launcher extends Activity } } + @TargetApi(Build.VERSION_CODES.M) private Bundle getActivityLaunchOptions(View v) { if (Utilities.ATLEAST_MARSHMALLOW) { int left = 0, top = 0; @@ -3081,6 +2976,12 @@ public class Launcher extends Activity } @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + mLastDispatchTouchEventX = ev.getX(); + return super.dispatchTouchEvent(ev); + } + + @Override public boolean onLongClick(View v) { if (!isDraggingEnabled()) return false; if (isWorkspaceLocked()) return false; @@ -3092,9 +2993,12 @@ public class Launcher extends Activity return true; } + boolean fromEdgeOfScreen = mLastDispatchTouchEventX < mEdgeOfScreenThresholdPx + || mLastDispatchTouchEventX > (mDeviceProfile.widthPx - mEdgeOfScreenThresholdPx); + if (v instanceof Workspace) { if (!mWorkspace.isInOverviewMode()) { - if (!mWorkspace.isTouchActive()) { + if (!mWorkspace.isTouchActive() && !fromEdgeOfScreen) { showOverviewMode(true); mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); @@ -3121,13 +3025,16 @@ public class Launcher extends Activity if (!mDragController.isDragging()) { if (itemUnderLongClick == null) { // User long pressed on empty space - mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); if (mWorkspace.isInOverviewMode()) { mWorkspace.startReordering(v); } else { + if (fromEdgeOfScreen) { + return false; + } showOverviewMode(true); } + mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); } else { final boolean isAllAppsButton = !FeatureFlags.NO_ALL_APPS_ICON && isHotseatLayout(v) && @@ -3135,17 +3042,7 @@ public class Launcher extends Activity longClickCellInfo.cellX, longClickCellInfo.cellY)); if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) { // User long pressed on an item - DragOptions dragOptions = new DragOptions(); - if (itemUnderLongClick instanceof BubbleTextView) { - BubbleTextView icon = (BubbleTextView) itemUnderLongClick; - if (icon.hasDeepShortcuts()) { - DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon); - if (dsc != null) { - dragOptions.deferDragCondition = dsc.createDeferDragCondition(null); - } - } - } - mWorkspace.startDrag(longClickCellInfo, dragOptions); + mWorkspace.startDrag(longClickCellInfo, new DragOptions()); } } } @@ -3224,10 +3121,6 @@ public class Launcher extends Activity // Change the state *after* we've called all the transition code mState = State.WORKSPACE; - // Resume the auto-advance of widgets - mUserPresent = true; - updateAutoAdvanceState(); - if (changed) { // Send an accessibility event to announce the context change getWindow().getDecorView() @@ -3333,9 +3226,6 @@ public class Launcher extends Activity // Change the state *after* we've called all the transition code mState = toState; - // Pause the auto-advance of widgets until we are out of AllApps - mUserPresent = false; - updateAutoAdvanceState(); closeFolder(); closeShortcutsContainer(); @@ -3561,7 +3451,6 @@ public class Launcher extends Activity mWorkspace.clearDropTargets(); mWorkspace.removeAllWorkspaceScreens(); - mWidgetsToAdvance.clear(); if (mHotseat != null) { mHotseat.resetLayout(); } @@ -3984,14 +3873,6 @@ public class Launcher extends Activity if (LauncherAppState.PROFILE_STARTUP) { Trace.beginSection("Page bind completed"); } - if (mSavedState != null) { - if (!mWorkspace.hasFocus()) { - mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus(); - } - - mSavedState = null; - } - mWorkspace.restoreInstanceStateForRemainingPages(); setWorkspaceLoading(false); @@ -4413,7 +4294,6 @@ public class Launcher extends Activity */ public void dumpState() { Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this); - Log.d(TAG, "mSavedState=" + mSavedState); Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading); Log.d(TAG, "mPendingRequestArgs=" + mPendingRequestArgs); Log.d(TAG, "mPendingActivityResult=" + mPendingActivityResult); @@ -4460,6 +4340,65 @@ public class Launcher extends Activity } } + @Override + @TargetApi(Build.VERSION_CODES.N) + public void onProvideKeyboardShortcuts( + List<KeyboardShortcutGroup> data, Menu menu, int deviceId) { + + ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>(); + if (mState == State.WORKSPACE) { + shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label), + KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON)); + } + View currentFocus = getCurrentFocus(); + if (new CustomActionsPopup(this, currentFocus).canShow()) { + shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.custom_actions), + KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON)); + } + if (currentFocus instanceof BubbleTextView && + ((BubbleTextView) currentFocus).hasDeepShortcuts()) { + shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.action_deep_shortcut), + KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON)); + } + if (!shortcutInfos.isEmpty()) { + data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos)); + } + + super.onProvideKeyboardShortcuts(data, menu, deviceId); + } + + @Override + public boolean onKeyShortcut(int keyCode, KeyEvent event) { + if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { + switch (keyCode) { + case KeyEvent.KEYCODE_A: + if (mState == State.WORKSPACE) { + showAppsView(true, true, false); + return true; + } + break; + case KeyEvent.KEYCODE_S: { + View focusedView = getCurrentFocus(); + if (focusedView instanceof BubbleTextView + && focusedView.getTag() instanceof ItemInfo + && mAccessibilityDelegate.performAction(focusedView, + (ItemInfo) focusedView.getTag(), + LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) { + getOpenShortcutsContainer().requestFocus(); + return true; + } + break; + } + case KeyEvent.KEYCODE_O: + if (new CustomActionsPopup(this, getCurrentFocus()).show()) { + return true; + } + break; + } + } + return super.onKeyShortcut(keyCode, event); + } + public static CustomAppWidget getCustomAppWidget(String name) { return sCustomAppWidgets.get(name); } @@ -4468,14 +4407,6 @@ public class Launcher extends Activity return sCustomAppWidgets; } - public static List<View> getFolderContents(View icon) { - if (icon instanceof FolderIcon) { - return ((FolderIcon) icon).getFolder().getItemsInReadingOrder(); - } else { - return Collections.EMPTY_LIST; - } - } - public static Launcher getLauncher(Context context) { if (context instanceof Launcher) { return (Launcher) context; diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java index ed1079ff3..fa5e519b1 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java @@ -20,6 +20,9 @@ import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.graphics.Rect; +import android.os.Handler; +import android.os.SystemClock; +import android.util.SparseBooleanArray; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -28,6 +31,7 @@ import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.Advanceable; import android.widget.RemoteViews; import com.android.launcher3.dragndrop.DragLayer.TouchCompleteListener; @@ -39,6 +43,13 @@ import java.util.ArrayList; */ public class LauncherAppWidgetHostView extends AppWidgetHostView implements TouchCompleteListener { + // Related to the auto-advancing of widgets + private static final long ADVANCE_INTERVAL = 20000; + private static final long ADVANCE_STAGGER = 250; + + // Maintains a list of widget ids which are supposed to be auto advanced. + private static final SparseBooleanArray sAutoAdvanceWidgetIds = new SparseBooleanArray(); + LayoutInflater mInflater; private CheckLongPressHelper mLongPressHelper; @@ -54,6 +65,10 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc protected int mErrorViewId = R.layout.appwidget_error; + private boolean mIsAttachedToWindow; + private boolean mIsAutoAdvanceRegistered; + private Runnable mAutoAdvanceRunnable; + public LauncherAppWidgetHostView(Context context) { super(context); mContext = context; @@ -78,6 +93,9 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc // Store the orientation in which the widget was inflated updateLastInflationOrientation(); super.updateAppWidget(remoteViews); + + // The provider info or the views might have changed. + checkIfAutoAdvance(); } public boolean isReinflateRequired() { @@ -153,6 +171,19 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc protected void onAttachedToWindow() { super.onAttachedToWindow(); mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + + mIsAttachedToWindow = true; + checkIfAutoAdvance(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + // We can't directly use isAttachedToWindow() here, as this is called before the internal + // state is updated. So isAttachedToWindow() will return true until next frame. + mIsAttachedToWindow = false; + checkIfAutoAdvance(); } @Override @@ -171,10 +202,6 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc return info; } - public LauncherAppWidgetProviderInfo getLauncherAppWidgetProviderInfo() { - return (LauncherAppWidgetProviderInfo) getAppWidgetInfo(); - } - @Override public void onTouchComplete() { if (!mLongPressHelper.hasPerformedLongPress()) { @@ -296,4 +323,79 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc super.onInitializeAccessibilityNodeInfo(info); info.setClassName(getClass().getName()); } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + maybeRegisterAutoAdvance(); + } + + private void checkIfAutoAdvance() { + boolean isAutoAdvance = false; + Advanceable target = getAdvanceable(); + if (target != null) { + isAutoAdvance = true; + target.fyiWillBeAdvancedByHostKThx(); + } + + boolean wasAutoAdvance = sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId()) >= 0; + if (isAutoAdvance != wasAutoAdvance) { + if (isAutoAdvance) { + sAutoAdvanceWidgetIds.put(getAppWidgetId(), true); + } else { + sAutoAdvanceWidgetIds.delete(getAppWidgetId()); + } + maybeRegisterAutoAdvance(); + } + } + + private Advanceable getAdvanceable() { + AppWidgetProviderInfo info = getAppWidgetInfo(); + if (info == null || info.autoAdvanceViewId == NO_ID || !mIsAttachedToWindow) { + return null; + } + View v = findViewById(info.autoAdvanceViewId); + return (v instanceof Advanceable) ? (Advanceable) v : null; + } + + private void maybeRegisterAutoAdvance() { + Handler handler = getHandler(); + boolean shouldRegisterAutoAdvance = getWindowVisibility() == VISIBLE && handler != null + && (sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId()) >= 0); + if (shouldRegisterAutoAdvance != mIsAutoAdvanceRegistered) { + mIsAutoAdvanceRegistered = shouldRegisterAutoAdvance; + if (mAutoAdvanceRunnable == null) { + mAutoAdvanceRunnable = new Runnable() { + @Override + public void run() { + runAutoAdvance(); + } + }; + } + + handler.removeCallbacks(mAutoAdvanceRunnable); + scheduleNextAdvance(); + } + } + + private void scheduleNextAdvance() { + if (!mIsAutoAdvanceRegistered) { + return; + } + long now = SystemClock.uptimeMillis(); + long advanceTime = now + (ADVANCE_INTERVAL - (now % ADVANCE_INTERVAL)) + + ADVANCE_STAGGER * sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId()); + Handler handler = getHandler(); + if (handler != null) { + handler.postAtTime(mAutoAdvanceRunnable, advanceTime); + } + } + + private void runAutoAdvance() { + Advanceable target = getAdvanceable(); + if (target != null) { + target.advance(); + } + scheduleNextAdvance(); + } } diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java deleted file mode 100644 index c1282b51c..000000000 --- a/src/com/android/launcher3/LauncherClings.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3; - -import android.content.Context; - -@Deprecated -public class LauncherClings { - private static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed"; - - public static void markFirstRunClingDismissed(Context ctx) { - Utilities.getPrefs(ctx).edit() - .putBoolean(WORKSPACE_CLING_DISMISSED_KEY, true) - .apply(); - } -} diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index d66ce6b91..4afff1898 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -57,8 +57,11 @@ import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.dynamicui.ExtractionUtils; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.logging.FileLog; +import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.GridSizeMigrationTask; +import com.android.launcher3.model.SdCardAvailableReceiver; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.provider.ImportDataTask; import com.android.launcher3.provider.LauncherDbUtils; @@ -69,12 +72,12 @@ import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.CursorIconInfo; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.GridOccupancy; +import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Preconditions; -import com.android.launcher3.util.StringFilter; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.ViewOnDrawExecutor; @@ -159,38 +162,11 @@ public class LauncherModel extends BroadcastReceiver } }; - // The lock that must be acquired before referencing any static bg data structures. Unlike - // other locks, this one can generally be held long-term because we never expect any of these - // static data structures to be referenced outside of the worker thread except on the first - // load after configuration change. - static final Object sBgLock = new Object(); - - // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by - // LauncherModel to their ids - static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>(); - - // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts - // created by LauncherModel that are directly on the home screen (however, no widgets or - // shortcuts within folders). - static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>(); - - // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget() - static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets = - new ArrayList<LauncherAppWidgetInfo>(); - - // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders() - static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>(); - - // sBgWorkspaceScreens is the ordered set of workspace screens. - static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>(); - - // sBgPinnedShortcutCounts is the ComponentKey representing a pinned shortcut to the number of - // times it is pinned. - static final Map<ShortcutKey, MutableInt> sBgPinnedShortcutCounts = new HashMap<>(); - - // sPendingPackages is a set of packages which could be on sdcard and are not available yet - static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages = - new HashMap<UserHandleCompat, HashSet<String>>(); + /** + * All the static data should be accessed on the background thread, A lock should be acquired + * on this object when accessing any data from this model. + */ + static final BgDataModel sBgDataModel = new BgDataModel(); // </ only access in worker thread > @@ -232,10 +208,6 @@ public class LauncherModel extends BroadcastReceiver public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap); } - public interface ItemInfoFilter { - public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn); - } - LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter, DeepShortcutManager deepShortcutManager) { Context context = app.getContext(); @@ -276,7 +248,7 @@ public class LauncherModel extends BroadcastReceiver @Override public void run() { - synchronized (sBgLock) { + synchronized (sBgDataModel) { final HashSet<ItemInfo> updates = new HashSet<>(); if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) { @@ -284,7 +256,7 @@ public class LauncherModel extends BroadcastReceiver return; } - for (ItemInfo info : sBgItemsIdMap) { + for (ItemInfo info : sBgDataModel.itemsIdMap) { if (info instanceof ShortcutInfo) { ShortcutInfo si = (ShortcutInfo) info; ComponentName cn = si.getTargetComponent(); @@ -301,7 +273,7 @@ public class LauncherModel extends BroadcastReceiver } } - for (LauncherAppWidgetInfo widget : sBgAppWidgets) { + for (LauncherAppWidgetInfo widget : sBgDataModel.appWidgets) { if (widget.providerName.getPackageName().equals(installInfo.packageName)) { widget.installProgress = installInfo.progress; updates.add(widget); @@ -334,11 +306,11 @@ public class LauncherModel extends BroadcastReceiver @Override public void run() { - synchronized (sBgLock) { + synchronized (sBgDataModel) { ArrayList<ShortcutInfo> updates = new ArrayList<>(); UserHandleCompat user = UserHandleCompat.myUserHandle(); - for (ItemInfo info : sBgItemsIdMap) { + for (ItemInfo info : sBgDataModel.itemsIdMap) { if (info instanceof ShortcutInfo) { ShortcutInfo si = (ShortcutInfo) info; ComponentName cn = si.getTargetComponent(); @@ -418,8 +390,8 @@ public class LauncherModel extends BroadcastReceiver // Use sBgItemsIdMap as all the items are already loaded. assertWorkspaceLoaded(); - synchronized (sBgLock) { - for (ItemInfo info : sBgItemsIdMap) { + synchronized (sBgDataModel) { + for (ItemInfo info : sBgDataModel.itemsIdMap) { if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { ArrayList<ItemInfo> items = screenItems.get(info.screenId); if (items == null) { @@ -496,7 +468,7 @@ public class LauncherModel extends BroadcastReceiver // can not use sBgWorkspaceScreens because loadWorkspace() may not have been // called. ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context); - synchronized(sBgLock) { + synchronized(sBgDataModel) { for (ItemInfo item : workspaceApps) { if (item instanceof ShortcutInfo) { // Short-circuit this logic if the icon exists somewhere on the workspace @@ -578,7 +550,7 @@ public class LauncherModel extends BroadcastReceiver static void checkItemInfoLocked( final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) { - ItemInfo modelItem = sBgItemsIdMap.get(itemId); + ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId); if (modelItem != null && item != modelItem) { // check all the data is consistent if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) { @@ -619,7 +591,7 @@ public class LauncherModel extends BroadcastReceiver final long itemId = item.id; Runnable r = new Runnable() { public void run() { - synchronized (sBgLock) { + synchronized (sBgDataModel) { checkItemInfoLocked(itemId, item, stackTrace); } } @@ -675,13 +647,13 @@ public class LauncherModel extends BroadcastReceiver static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) { // Lock on mBgLock *after* the db operation - synchronized (sBgLock) { + synchronized (sBgDataModel) { checkItemInfoLocked(itemId, item, stackTrace); if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP && item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) { // Item is in a folder, make sure this folder exists - if (!sBgFolders.containsKey(item.container)) { + if (!sBgDataModel.folders.containsKey(item.container)) { // An items container is being set to a that of an item which is not in // the list of Folders. String msg = "item: " + item + " container being set to: " + @@ -693,7 +665,7 @@ public class LauncherModel extends BroadcastReceiver // Items are added/removed from the corresponding FolderInfo elsewhere, such // as in Workspace.onDrop. Here, we just add/remove them from the list of items // that are on the desktop, as appropriate - ItemInfo modelItem = sBgItemsIdMap.get(itemId); + ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId); if (modelItem != null && (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) { @@ -702,15 +674,15 @@ public class LauncherModel extends BroadcastReceiver case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - if (!sBgWorkspaceItems.contains(modelItem)) { - sBgWorkspaceItems.add(modelItem); + if (!sBgDataModel.workspaceItems.contains(modelItem)) { + sBgDataModel.workspaceItems.add(modelItem); } break; default: break; } } else { - sBgWorkspaceItems.remove(modelItem); + sBgDataModel.workspaceItems.remove(modelItem); } } } @@ -854,8 +826,8 @@ public class LauncherModel extends BroadcastReceiver intentWithoutPkg = intent.toUri(0); } - synchronized (sBgLock) { - for (ItemInfo item : sBgItemsIdMap) { + synchronized (sBgDataModel) { + for (ItemInfo item : sBgDataModel.itemsIdMap) { if (item instanceof ShortcutInfo) { ShortcutInfo info = (ShortcutInfo) item; Intent targetIntent = info.promisedIntent == null @@ -906,76 +878,35 @@ public class LauncherModel extends BroadcastReceiver public void run() { cr.insert(LauncherSettings.Favorites.CONTENT_URI, values); - // Lock on mBgLock *after* the db operation - synchronized (sBgLock) { + synchronized (sBgDataModel) { checkItemInfoLocked(item.id, item, stackTrace); - sBgItemsIdMap.put(item.id, item); - switch (item.itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - sBgFolders.put(item.id, (FolderInfo) item); - // Fall through - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: - if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || - item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - sBgWorkspaceItems.add(item); - } else { - if (!sBgFolders.containsKey(item.container)) { - // Adding an item to a folder that doesn't exist. - String msg = "adding item: " + item + " to a folder that " + - " doesn't exist"; - Log.e(TAG, msg); - } - } - if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { - incrementPinnedShortcutCount( - ShortcutKey.fromShortcutInfo((ShortcutInfo) item), - true /* shouldPin */); - } - break; - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - sBgAppWidgets.add((LauncherAppWidgetInfo) item); - break; - } + sBgDataModel.addItem(item, true); } } }; runOnWorkerThread(r); } - private static ArrayList<ItemInfo> getItemsByPackageName( - final String pn, final UserHandleCompat user) { - ItemInfoFilter filter = new ItemInfoFilter() { - @Override - public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) { - return cn.getPackageName().equals(pn) && info.user.equals(user); - } - }; - return filterItemInfos(sBgItemsIdMap, filter); - } - - /** - * Removes all the items from the database corresponding to the specified package. - */ - static void deletePackageFromDatabase(Context context, final String pn, - final UserHandleCompat user) { - deleteItemsFromDatabase(context, getItemsByPackageName(pn, user)); - } - /** * Removes the specified item from the database */ public static void deleteItemFromDatabase(Context context, final ItemInfo item) { - ArrayList<ItemInfo> items = new ArrayList<ItemInfo>(); + ArrayList<ItemInfo> items = new ArrayList<>(); items.add(item); deleteItemsFromDatabase(context, items); } /** + * Removes all the items from the database matching {@param matcher}. + */ + public static void deleteItemsFromDatabase(Context context, ItemInfoMatcher matcher) { + deleteItemsFromDatabase(context, matcher.filterItemInfos(sBgDataModel.itemsIdMap)); + } + + /** * Removes the specified items from the database */ - static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) { + static void deleteItemsFromDatabase(Context context, final Iterable<? extends ItemInfo> items) { final ContentResolver cr = context.getContentResolver(); Runnable r = new Runnable() { public void run() { @@ -983,36 +914,7 @@ public class LauncherModel extends BroadcastReceiver final Uri uri = LauncherSettings.Favorites.getContentUri(item.id); cr.delete(uri, null, null); - // Lock on mBgLock *after* the db operation - synchronized (sBgLock) { - switch (item.itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - sBgFolders.remove(item.id); - for (ItemInfo info: sBgItemsIdMap) { - if (info.container == item.id) { - // We are deleting a folder which still contains items that - // think they are contained by that folder. - String msg = "deleting a folder (" + item + ") which still " + - "contains items (" + info + ")"; - Log.e(TAG, msg); - } - } - sBgWorkspaceItems.remove(item); - break; - case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: - decrementPinnedShortcutCount(ShortcutKey.fromShortcutInfo( - (ShortcutInfo) item)); - // Fall through. - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - sBgWorkspaceItems.remove(item); - break; - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - sBgAppWidgets.remove((LauncherAppWidgetInfo) item); - break; - } - sBgItemsIdMap.remove(item.id); - } + sBgDataModel.removeItem(item); } } }; @@ -1020,39 +922,6 @@ public class LauncherModel extends BroadcastReceiver } /** - * Decrement the count for the given pinned shortcut, unpinning it if the count becomes 0. - */ - private static void decrementPinnedShortcutCount(final ShortcutKey pinnedShortcut) { - synchronized (sBgLock) { - MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut); - if (count == null || --count.value == 0) { - LauncherAppState.getInstance().getShortcutManager().unpinShortcut(pinnedShortcut); - } - } - } - - /** - * Increment the count for the given shortcut, pinning it if the count becomes 1. - * - * As an optimization, the caller can pass shouldPin == false to avoid - * unnecessary RPC's if the shortcut is already pinned. - */ - private static void incrementPinnedShortcutCount(ShortcutKey pinnedShortcut, boolean shouldPin) { - synchronized (sBgLock) { - MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut); - if (count == null) { - count = new MutableInt(1); - sBgPinnedShortcutCounts.put(pinnedShortcut, count); - } else { - count.value++; - } - if (shouldPin && count.value == 1) { - LauncherAppState.getInstance().getShortcutManager().pinShortcut(pinnedShortcut); - } - } - } - - /** * Update the order of the workspace screens in the database. The array list contains * a list of screen ids in the order that they should appear. */ @@ -1091,9 +960,9 @@ public class LauncherModel extends BroadcastReceiver throw new RuntimeException(ex); } - synchronized (sBgLock) { - sBgWorkspaceScreens.clear(); - sBgWorkspaceScreens.addAll(screensCopy); + synchronized (sBgDataModel) { + sBgDataModel.workspaceScreens.clear(); + sBgDataModel.workspaceScreens.addAll(screensCopy); } } }; @@ -1108,22 +977,13 @@ public class LauncherModel extends BroadcastReceiver Runnable r = new Runnable() { public void run() { - cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null); - // Lock on mBgLock *after* the db operation - synchronized (sBgLock) { - sBgItemsIdMap.remove(info.id); - sBgFolders.remove(info.id); - sBgWorkspaceItems.remove(info); - } - cr.delete(LauncherSettings.Favorites.CONTENT_URI, LauncherSettings.Favorites.CONTAINER + "=" + info.id, null); - // Lock on mBgLock *after* the db operation - synchronized (sBgLock) { - for (ItemInfo childInfo : info.contents) { - sBgItemsIdMap.remove(childInfo.id); - } - } + sBgDataModel.removeItem(info.contents); + info.contents.clear(); + + cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null); + sBgDataModel.removeItem(info); } }; runOnWorkerThread(r); @@ -1150,9 +1010,12 @@ public class LauncherModel extends BroadcastReceiver @Override public void onPackageRemoved(String packageName, UserHandleCompat user) { + onPackagesRemoved(user, packageName); + } + + public void onPackagesRemoved(UserHandleCompat user, String... packages) { int op = PackageUpdatedTask.OP_REMOVE; - enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName }, - user)); + enqueueItemUpdatedTask(new PackageUpdatedTask(op, packages, user)); } @Override @@ -1639,18 +1502,6 @@ public class LauncherModel extends BroadcastReceiver } } - /** Clears all the sBg data structures */ - private void clearSBgDataStructures() { - synchronized (sBgLock) { - sBgWorkspaceItems.clear(); - sBgAppWidgets.clear(); - sBgFolders.clear(); - sBgItemsIdMap.clear(); - sBgWorkspaceScreens.clear(); - sBgPinnedShortcutCounts.clear(); - } - } - private void loadWorkspace() { if (LauncherAppState.PROFILE_STARTUP) { Trace.beginSection("Loading Workspace"); @@ -1663,6 +1514,7 @@ public class LauncherModel extends BroadcastReceiver final boolean isSafeMode = manager.isSafeMode(); final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); final boolean isSdCardReady = Utilities.isBootCompleted(); + final MultiHashMap<UserHandleCompat, String> pendingPackages = new MultiHashMap<>(); LauncherAppState app = LauncherAppState.getInstance(); InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); @@ -1693,11 +1545,12 @@ public class LauncherModel extends BroadcastReceiver LauncherSettings.Settings.call(contentResolver, LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES); - synchronized (sBgLock) { - clearSBgDataStructures(); + synchronized (sBgDataModel) { + sBgDataModel.clear(); + final HashMap<String, Integer> installingPkgs = PackageInstallerCompat .getInstance(mContext).updateAndGetActiveSessionCache(); - sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext)); + sBgDataModel.workspaceScreens.addAll(loadWorkspaceScreensDb(mContext)); final ArrayList<Long> itemsToRemove = new ArrayList<>(); final ArrayList<Long> restoredRows = new ArrayList<>(); @@ -1901,12 +1754,7 @@ public class LauncherModel extends BroadcastReceiver // SdCard is not ready yet. Package might get available, // once it is ready. Log.d(TAG, "Invalid package: " + cn + " (check again later)"); - HashSet<String> pkgs = sPendingPackages.get(user); - if (pkgs == null) { - pkgs = new HashSet<String>(); - sPendingPackages.put(user, pkgs); - } - pkgs.add(cn.getPackageName()); + pendingPackages.addToList(user, cn.getPackageName()); allowMissingTarget = true; // Add the icon on the workspace anyway. @@ -1977,7 +1825,6 @@ public class LauncherModel extends BroadcastReceiver info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER; } - incrementPinnedShortcutCount(key, false /* shouldPin */); } else { // item type == ITEM_TYPE_SHORTCUT info = getShortcutInfo(c, cursorIconInfo); @@ -2019,7 +1866,7 @@ public class LauncherModel extends BroadcastReceiver } // check & update map of what's occupied - if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) { + if (!checkItemPlacement(occupied, info, sBgDataModel.workspaceScreens)) { itemsToRemove.add(id); break; } @@ -2036,19 +1883,7 @@ public class LauncherModel extends BroadcastReceiver } } - switch (container) { - case LauncherSettings.Favorites.CONTAINER_DESKTOP: - case LauncherSettings.Favorites.CONTAINER_HOTSEAT: - sBgWorkspaceItems.add(info); - break; - default: - // Item is in a user folder - FolderInfo folderInfo = - findOrMakeFolder(sBgFolders, container); - folderInfo.add(info, false); - break; - } - sBgItemsIdMap.put(info.id, info); + sBgDataModel.addItem(info, false); } else { throw new RuntimeException("Unexpected null ShortcutInfo"); } @@ -2056,7 +1891,7 @@ public class LauncherModel extends BroadcastReceiver case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: id = c.getLong(idIndex); - FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id); + FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(id); // Do not trim the folder label, as is was set by the user. folderInfo.title = c.getString(cursorIconInfo.titleIndex); @@ -2070,25 +1905,16 @@ public class LauncherModel extends BroadcastReceiver folderInfo.options = c.getInt(optionsIndex); // check & update map of what's occupied - if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) { + if (!checkItemPlacement(occupied, folderInfo, sBgDataModel.workspaceScreens)) { itemsToRemove.add(id); break; } - - switch (container) { - case LauncherSettings.Favorites.CONTAINER_DESKTOP: - case LauncherSettings.Favorites.CONTAINER_HOTSEAT: - sBgWorkspaceItems.add(folderInfo); - break; - } - if (restored) { // no special handling required for restored folders restoredRows.add(id); } - sBgItemsIdMap.put(folderInfo.id, folderInfo); - sBgFolders.put(folderInfo.id, folderInfo); + sBgDataModel.addItem(folderInfo, false); break; case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: @@ -2205,7 +2031,7 @@ public class LauncherModel extends BroadcastReceiver appWidgetInfo.container = container; // check & update map of what's occupied - if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) { + if (!checkItemPlacement(occupied, appWidgetInfo, sBgDataModel.workspaceScreens)) { itemsToRemove.add(id); break; } @@ -2224,8 +2050,7 @@ public class LauncherModel extends BroadcastReceiver updateItem(id, values); } } - sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); - sBgAppWidgets.add(appWidgetInfo); + sBgDataModel.addItem(appWidgetInfo, false); } break; } @@ -2239,7 +2064,7 @@ public class LauncherModel extends BroadcastReceiver // Break early if we've stopped loading if (mStopped) { - clearSBgDataStructures(); + sBgDataModel.clear(); return; } @@ -2259,15 +2084,15 @@ public class LauncherModel extends BroadcastReceiver LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS) .getSerializable(LauncherSettings.Settings.EXTRA_VALUE); for (long folderId : deletedFolderIds) { - sBgWorkspaceItems.remove(sBgFolders.get(folderId)); - sBgFolders.remove(folderId); - sBgItemsIdMap.remove(folderId); + sBgDataModel.workspaceItems.remove(sBgDataModel.folders.get(folderId)); + sBgDataModel.folders.remove(folderId); + sBgDataModel.itemsIdMap.remove(folderId); } } // Unpin shortcuts that don't exist on the workspace. for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) { - MutableInt numTimesPinned = sBgPinnedShortcutCounts.get(key); + MutableInt numTimesPinned = sBgDataModel.pinnedShortcutCounts.get(key); if (numTimesPinned == null || numTimesPinned.value == 0) { // Shortcut is pinned but doesn't exist on the workspace; unpin it. mDeepShortcutManager.unpinShortcut(key); @@ -2275,7 +2100,7 @@ public class LauncherModel extends BroadcastReceiver } // Sort all the folder items and make sure the first 3 items are high resolution. - for (FolderInfo folder : sBgFolders) { + for (FolderInfo folder : sBgDataModel.folders) { Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR); int pos = 0; for (ShortcutInfo info : folder.contents) { @@ -2298,15 +2123,18 @@ public class LauncherModel extends BroadcastReceiver LauncherSettings.Favorites._ID, restoredRows), null); } - if (!isSdCardReady && !sPendingPackages.isEmpty()) { - context.registerReceiver(new AppsAvailabilityCheck(), + if (!isSdCardReady && !pendingPackages.isEmpty()) { + context.registerReceiver( + new SdCardAvailableReceiver( + LauncherModel.this, mContext, pendingPackages), new IntentFilter(Intent.ACTION_BOOT_COMPLETED), - null, sWorker); + null, + sWorker); } // Remove any empty screens - ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens); - for (ItemInfo item: sBgItemsIdMap) { + ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgDataModel.workspaceScreens); + for (ItemInfo item: sBgDataModel.itemsIdMap) { long screenId = item.screenId; if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && unusedScreens.contains(screenId)) { @@ -2316,8 +2144,8 @@ public class LauncherModel extends BroadcastReceiver // If there are any empty screens remove them, and update. if (unusedScreens.size() != 0) { - sBgWorkspaceScreens.removeAll(unusedScreens); - updateWorkspaceScreenOrder(context, sBgWorkspaceScreens); + sBgDataModel.workspaceScreens.removeAll(unusedScreens); + updateWorkspaceScreenOrder(context, sBgDataModel.workspaceScreens); } if (DEBUG_LOADERS) { @@ -2530,10 +2358,10 @@ public class LauncherModel extends BroadcastReceiver ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); ArrayList<Long> orderedScreenIds = new ArrayList<>(); - synchronized (sBgLock) { - workspaceItems.addAll(sBgWorkspaceItems); - appWidgets.addAll(sBgAppWidgets); - orderedScreenIds.addAll(sBgWorkspaceScreens); + synchronized (sBgDataModel) { + workspaceItems.addAll(sBgDataModel.workspaceItems); + appWidgets.addAll(sBgDataModel.appWidgets); + orderedScreenIds.addAll(sBgDataModel.workspaceScreens); } final int currentScreen; @@ -2677,8 +2505,8 @@ public class LauncherModel extends BroadcastReceiver private void updateIconCache() { // Ignore packages which have a promise icon. HashSet<String> packagesToIgnore = new HashSet<>(); - synchronized (sBgLock) { - for (ItemInfo info : sBgItemsIdMap) { + synchronized (sBgDataModel) { + for (ItemInfo info : sBgDataModel.itemsIdMap) { if (info instanceof ShortcutInfo) { ShortcutInfo si = (ShortcutInfo) info; if (si.isPromise() && si.getTargetComponent() != null) { @@ -2841,11 +2669,11 @@ public class LauncherModel extends BroadcastReceiver } public void dumpState() { - synchronized (sBgLock) { + synchronized (sBgDataModel) { Log.d(TAG, "mLoaderTask.mContext=" + mContext); Log.d(TAG, "mLoaderTask.mStopped=" + mStopped); Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished); - Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size()); + Log.d(TAG, "mItems size=" + sBgDataModel.workspaceItems.size()); } } } @@ -2914,8 +2742,8 @@ public class LauncherModel extends BroadcastReceiver // If any package icon has changed (app was updated while launcher was dead), // update the corresponding shortcuts. - synchronized (sBgLock) { - for (ItemInfo info : sBgItemsIdMap) { + synchronized (sBgDataModel) { + for (ItemInfo info : sBgDataModel.itemsIdMap) { if (info instanceof ShortcutInfo && user.equals(info.user) && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { ShortcutInfo si = (ShortcutInfo) info; @@ -2971,47 +2799,10 @@ public class LauncherModel extends BroadcastReceiver sWorker.post(task); } - @Thunk class AppsAvailabilityCheck extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - synchronized (sBgLock) { - final LauncherAppsCompat launcherApps = LauncherAppsCompat - .getInstance(mApp.getContext()); - final PackageManager manager = context.getPackageManager(); - final ArrayList<String> packagesRemoved = new ArrayList<String>(); - final ArrayList<String> packagesUnavailable = new ArrayList<String>(); - for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) { - UserHandleCompat user = entry.getKey(); - packagesRemoved.clear(); - packagesUnavailable.clear(); - for (String pkg : entry.getValue()) { - if (!launcherApps.isPackageEnabledForProfile(pkg, user)) { - if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) { - packagesUnavailable.add(pkg); - } else { - packagesRemoved.add(pkg); - } - } - } - if (!packagesRemoved.isEmpty()) { - enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE, - packagesRemoved.toArray(new String[packagesRemoved.size()]), user)); - } - if (!packagesUnavailable.isEmpty()) { - enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE, - packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user)); - } - } - sPendingPackages.clear(); - } - } - } - private class PackageUpdatedTask implements Runnable { - int mOp; - String[] mPackages; - UserHandleCompat mUser; + final int mOp; + final String[] mPackages; + final UserHandleCompat mUser; public static final int OP_NONE = 0; public static final int OP_ADD = 1; @@ -3038,7 +2829,7 @@ public class LauncherModel extends BroadcastReceiver final String[] packages = mPackages; final int N = packages.length; FlagOp flagOp = FlagOp.NO_OP; - StringFilter pkgFilter = StringFilter.of(new HashSet<>(Arrays.asList(packages))); + final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages)); switch (mOp) { case OP_ADD: { for (int i=0; i<N; i++) { @@ -3088,15 +2879,15 @@ public class LauncherModel extends BroadcastReceiver FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED); if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.(un)suspend " + N); - mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp); + mBgAllAppsList.updateDisabledFlags( + ItemInfoMatcher.ofPackages(packageSet, mUser), flagOp); break; case OP_USER_AVAILABILITY_CHANGE: flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser) ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER) : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER); // We want to update all packages for this user. - pkgFilter = StringFilter.matchesAll(); - mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp); + mBgAllAppsList.updateDisabledFlags(ItemInfoMatcher.ofUser(mUser), flagOp); break; } @@ -3145,12 +2936,12 @@ public class LauncherModel extends BroadcastReceiver // Update shortcut infos if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) { - final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>(); - final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>(); - final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>(); + final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>(); + final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<>(); + final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>(); - synchronized (sBgLock) { - for (ItemInfo info : sBgItemsIdMap) { + synchronized (sBgDataModel) { + for (ItemInfo info : sBgDataModel.itemsIdMap) { if (info instanceof ShortcutInfo && mUser.equals(info.user)) { ShortcutInfo si = (ShortcutInfo) info; boolean infoUpdated = false; @@ -3158,8 +2949,8 @@ public class LauncherModel extends BroadcastReceiver // Update shortcuts which use iconResource. if ((si.iconResource != null) - && pkgFilter.matches(si.iconResource.packageName)) { - Bitmap icon = Utilities.createIconBitmap( + && packageSet.contains(si.iconResource.packageName)) { + Bitmap icon = LauncherIcons.createIconBitmap( si.iconResource.packageName, si.iconResource.resourceName, context); if (icon != null) { @@ -3170,7 +2961,7 @@ public class LauncherModel extends BroadcastReceiver } ComponentName cn = si.getTargetComponent(); - if (cn != null && pkgFilter.matches(cn.getPackageName())) { + if (cn != null && packageSet.contains(cn.getPackageName())) { AppInfo appInfo = addedOrUpdatedApps.get(cn); if (si.isPromise()) { @@ -3235,7 +3026,7 @@ public class LauncherModel extends BroadcastReceiver LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info; if (mUser.equals(widgetInfo.user) && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) - && pkgFilter.matches(widgetInfo.providerName.getPackageName())) { + && packageSet.contains(widgetInfo.providerName.getPackageName())) { widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; @@ -3293,12 +3084,10 @@ public class LauncherModel extends BroadcastReceiver } if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) { - for (String pn : removedPackages) { - deletePackageFromDatabase(context, pn, mUser); - } - for (ComponentName cn : removedComponents) { - deleteItemsFromDatabase(context, getItemInfoForComponentName(cn, mUser)); - } + deleteItemsFromDatabase( + context, ItemInfoMatcher.ofPackages(removedPackages, mUser)); + deleteItemsFromDatabase( + context, ItemInfoMatcher.ofComponents(removedComponents, mUser)); // Remove any queued items from the install queue InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser); @@ -3383,7 +3172,7 @@ public class LauncherModel extends BroadcastReceiver // Find ShortcutInfo's that have changed on the workspace. final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>(); MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>(); - for (ItemInfo itemInfo : sBgItemsIdMap) { + for (ItemInfo itemInfo : sBgDataModel.itemsIdMap) { if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { ShortcutInfo si = (ShortcutInfo) itemInfo; if (si.getPromisedIntent().getPackage().equals(mPackageName) @@ -3473,7 +3262,7 @@ public class LauncherModel extends BroadcastReceiver // Update the workspace to reflect the changes to updated shortcuts residing on it. ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>(); ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>(); - for (ItemInfo itemInfo : sBgItemsIdMap) { + for (ItemInfo itemInfo : sBgDataModel.itemsIdMap) { if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT && mUser.equals(itemInfo.user)) { ShortcutInfo si = (ShortcutInfo) itemInfo; @@ -3549,18 +3338,6 @@ public class LauncherModel extends BroadcastReceiver return !launcherApps.isPackageEnabledForProfile(packageName, user); } - public static boolean isValidPackageActivity(Context context, ComponentName cn, - UserHandleCompat user) { - if (cn == null) { - return false; - } - final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); - if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) { - return false; - } - return launcherApps.isActivityEnabledForProfile(cn, user); - } - public static boolean isValidPackage(Context context, String packageName, UserHandleCompat user) { if (packageName == null) { @@ -3683,50 +3460,6 @@ public class LauncherModel extends BroadcastReceiver return info; } - static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos, - ItemInfoFilter f) { - HashSet<ItemInfo> filtered = new HashSet<ItemInfo>(); - for (ItemInfo i : infos) { - if (i instanceof ShortcutInfo) { - ShortcutInfo info = (ShortcutInfo) i; - ComponentName cn = info.getTargetComponent(); - if (cn != null && f.filterItem(null, info, cn)) { - filtered.add(info); - } - } else if (i instanceof FolderInfo) { - FolderInfo info = (FolderInfo) i; - for (ShortcutInfo s : info.contents) { - ComponentName cn = s.getTargetComponent(); - if (cn != null && f.filterItem(info, s, cn)) { - filtered.add(s); - } - } - } else if (i instanceof LauncherAppWidgetInfo) { - LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i; - ComponentName cn = info.providerName; - if (cn != null && f.filterItem(null, info, cn)) { - filtered.add(info); - } - } - } - return new ArrayList<ItemInfo>(filtered); - } - - @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname, - final UserHandleCompat user) { - ItemInfoFilter filter = new ItemInfoFilter() { - @Override - public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) { - if (info.user == null) { - return cn.equals(cname); - } else { - return cn.equals(cname) && info.user.equals(user); - } - } - }; - return filterItemInfos(sBgItemsIdMap, filter); - } - /** * Make an ShortcutInfo object for a shortcut that isn't an application. */ @@ -3768,17 +3501,15 @@ public class LauncherModel extends BroadcastReceiver } Bitmap icon = null; - boolean customIcon = false; ShortcutIconResource iconResource = null; if (bitmap instanceof Bitmap) { - icon = Utilities.createIconBitmap((Bitmap) bitmap, context); - customIcon = true; + icon = LauncherIcons.createIconBitmap((Bitmap) bitmap, context); } else { Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); if (extra instanceof ShortcutIconResource) { iconResource = (ShortcutIconResource) extra; - icon = Utilities.createIconBitmap(iconResource.packageName, + icon = LauncherIcons.createIconBitmap(iconResource.packageName, iconResource.resourceName, context); } } @@ -3802,22 +3533,6 @@ public class LauncherModel extends BroadcastReceiver return info; } - /** - * Return an existing FolderInfo object if we have encountered this ID previously, - * or make a new one. - */ - @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) { - // See if a placeholder was created for us already - FolderInfo folderInfo = folders.get(id); - if (folderInfo == null) { - // No placeholder -- create a new instance - folderInfo = new FolderInfo(); - folders.put(id, folderInfo); - } - return folderInfo; - } - - static boolean isValidProvider(AppWidgetProviderInfo provider) { return (provider != null) && (provider.provider != null) && (provider.provider.getPackageName() != null); @@ -3844,8 +3559,8 @@ public class LauncherModel extends BroadcastReceiver * @return {@link FolderInfo} if its already loaded. */ public FolderInfo findFolderById(Long folderId) { - synchronized (sBgLock) { - return sBgFolders.get(folderId); + synchronized (sBgDataModel) { + return sBgDataModel.folders.get(folderId); } } diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index f3d949326..e1ff6dbc1 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -764,12 +764,7 @@ public class LauncherProvider extends ContentProvider { } } case 16: { - // We use the db version upgrade here to identify users who may not have seen - // clings yet (because they weren't available), but for whom the clings are now - // available (tablet users). Because one of the possible cling flows (migration) - // is very destructive (wipes out workspaces), we want to prevent this from showing - // until clear data. We do so by marking that the clings have been shown. - LauncherClings.markFirstRunClingDismissed(mContext); + // No-op } case 17: { // No-op diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index bea55d215..0b9bf09df 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -98,7 +98,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc @ViewDebug.ExportedProperty(category = "launcher") protected int mCurrentPage; - protected int mRestorePage = INVALID_RESTORE_PAGE; private int mChildCountOnLastLayout; @ViewDebug.ExportedProperty(category = "launcher") @@ -418,17 +417,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } /** - * The restore page will be set in place of the current page at the next (likely first) - * layout. - */ - void setRestorePage(int restorePage) { - mRestorePage = restorePage; - } - int getRestorePage() { - return mRestorePage; - } - - /** * Should be called whenever the page changes. In the case of a scroll, we wait until the page * has settled. */ @@ -879,12 +867,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } if (mScroller.isFinished() && mChildCountOnLastLayout != childCount) { - if (mRestorePage != INVALID_RESTORE_PAGE) { - setCurrentPage(mRestorePage); - mRestorePage = INVALID_RESTORE_PAGE; - } else { - setCurrentPage(getNextPage()); - } + setCurrentPage(getNextPage()); } mChildCountOnLastLayout = childCount; diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java index 48a75d111..bc5ac2456 100644 --- a/src/com/android/launcher3/PinchToOverviewListener.java +++ b/src/com/android/launcher3/PinchToOverviewListener.java @@ -61,12 +61,12 @@ public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleG mPinchDetector = new ScaleGestureDetector((Context) mLauncher, this); } - public boolean onInterceptTouchEvent(MotionEvent ev) { + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { mPinchDetector.onTouchEvent(ev); return mPinchStarted; } - public boolean onTouchEvent(MotionEvent ev) { + public boolean onControllerTouchEvent(MotionEvent ev) { if (mPinchStarted) { if (ev.getPointerCount() > 2) { // Using more than two fingers causes weird behavior, so just cancel the pinch. diff --git a/src/com/android/launcher3/QsbContainerView.java b/src/com/android/launcher3/QsbContainerView.java index 7d939a0eb..65711e127 100644 --- a/src/com/android/launcher3/QsbContainerView.java +++ b/src/com/android/launcher3/QsbContainerView.java @@ -109,6 +109,8 @@ public class QsbContainerView extends FrameLayout { if (mWidgetInfo == null) { // There is no search provider, just show the default widget. return getDefaultView(inflater, container, false); + } else { + mWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(launcher, mWidgetInfo); } SharedPreferences prefs = Utilities.getPrefs(launcher); diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index fb9374314..ce0629136 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -31,6 +31,7 @@ import com.android.launcher3.compat.LauncherActivityInfoCompat; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.shortcuts.ShortcutInfoCompat; /** @@ -323,13 +324,13 @@ public class ShortcutInfo extends ItemInfo { IconCache cache = launcherAppState.getIconCache(); Bitmap unbadgedBitmap = unbadgedDrawable == null ? cache.getDefaultIcon(UserHandleCompat.myUserHandle()) - : Utilities.createScaledBitmapWithoutShadow(unbadgedDrawable, context); + : LauncherIcons.createScaledBitmapWithoutShadow(unbadgedDrawable, context); setIcon(getBadgedIcon(unbadgedBitmap, shortcutInfo, cache, context)); } protected Bitmap getBadgedIcon(Bitmap unbadgedBitmap, ShortcutInfoCompat shortcutInfo, IconCache cache, Context context) { - unbadgedBitmap = Utilities.addShadowToIcon(unbadgedBitmap); + unbadgedBitmap = LauncherIcons.addShadowToIcon(unbadgedBitmap); // Get the app info for the source activity. AppInfo appInfo = new AppInfo(); appInfo.user = user; @@ -338,9 +339,9 @@ public class ShortcutInfo extends ItemInfo { cache.getTitleAndIcon(appInfo, shortcutInfo.getActivityInfo(context), false); } catch (NullPointerException e) { // This may happen when we fail to load the activity info. Worst case ignore badging. - return Utilities.badgeIconForUser(unbadgedBitmap, user, context); + return LauncherIcons.badgeIconForUser(unbadgedBitmap, user, context); } - return Utilities.badgeWithBitmap(unbadgedBitmap, appInfo.iconBitmap, context); + return LauncherIcons.badgeWithBitmap(unbadgedBitmap, appInfo.iconBitmap, context); } /** Returns the ShortcutInfo id associated with the deep shortcut. */ diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index b0e096a2e..b4f0a498c 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -31,19 +31,11 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.TypedArray; -import android.database.Cursor; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; -import android.graphics.PaintFlagsDrawFilter; import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.PaintDrawable; import android.os.Build; import android.os.Bundle; import android.os.PowerManager; @@ -62,11 +54,7 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; -import com.android.launcher3.compat.UserHandleCompat; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.ProviderConfig; -import com.android.launcher3.graphics.ShadowGenerator; -import com.android.launcher3.util.IconNormalizer; import java.io.ByteArrayOutputStream; import java.io.Closeable; @@ -90,19 +78,9 @@ public final class Utilities { private static final String TAG = "Launcher.Utilities"; - private static final Rect sOldBounds = new Rect(); - private static final Canvas sCanvas = new Canvas(); - private static final Pattern sTrimPattern = Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$"); - static { - sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, - Paint.FILTER_BITMAP_FLAG)); - } - static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff }; - static int sColorIndex = 0; - private static final int[] sLoc0 = new int[2]; private static final int[] sLoc1 = new int[2]; @@ -170,198 +148,6 @@ public final class Utilities { return false; } - public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) { - byte[] data = c.getBlob(iconIndex); - try { - return createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), context); - } catch (Exception e) { - return null; - } - } - - /** - * Returns a bitmap suitable for the all apps view. If the package or the resource do not - * exist, it returns null. - */ - public static Bitmap createIconBitmap(String packageName, String resourceName, - Context context) { - PackageManager packageManager = context.getPackageManager(); - // the resource - try { - Resources resources = packageManager.getResourcesForApplication(packageName); - if (resources != null) { - final int id = resources.getIdentifier(resourceName, null, null); - return createIconBitmap( - resources.getDrawableForDensity(id, LauncherAppState.getInstance() - .getInvariantDeviceProfile().fillResIconDpi), context); - } - } catch (Exception e) { - // Icon not found. - } - return null; - } - - private static int getIconBitmapSize() { - return LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize; - } - - /** - * Returns a bitmap which is of the appropriate size to be displayed as an icon - */ - public static Bitmap createIconBitmap(Bitmap icon, Context context) { - final int iconBitmapSize = getIconBitmapSize(); - if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) { - return icon; - } - return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context); - } - - /** - * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}. - * The bitmap is also visually normalized with other icons. - */ - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public static Bitmap createBadgedIconBitmap( - Drawable icon, UserHandleCompat user, Context context) { - float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? - 1 : IconNormalizer.getInstance().getScale(icon, null); - Bitmap bitmap = createIconBitmap(icon, context, scale); - return badgeIconForUser(bitmap, user, context); - } - - /** - * Badges the provided icon with the user badge if required. - */ - public static Bitmap badgeIconForUser(Bitmap icon, UserHandleCompat user, Context context) { - if (Utilities.ATLEAST_LOLLIPOP && user != null - && !UserHandleCompat.myUserHandle().equals(user)) { - BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon); - Drawable badged = context.getPackageManager().getUserBadgedIcon( - drawable, user.getUser()); - if (badged instanceof BitmapDrawable) { - return ((BitmapDrawable) badged).getBitmap(); - } else { - return createIconBitmap(badged, context); - } - } else { - return icon; - } - } - - /** - * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually - * normalized with other icons and has enough spacing to add shadow. - */ - public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context) { - RectF iconBounds = new RectF(); - float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? - 1 : IconNormalizer.getInstance().getScale(icon, iconBounds); - scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds)); - return createIconBitmap(icon, context, scale); - } - - /** - * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using - * {@link #createScaledBitmapWithoutShadow(Drawable, Context)} - */ - public static Bitmap addShadowToIcon(Bitmap icon) { - return ShadowGenerator.getInstance().recreateIcon(icon); - } - - /** - * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions. - */ - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) { - int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size); - synchronized (sCanvas) { - sCanvas.setBitmap(srcTgt); - sCanvas.drawBitmap(badge, new Rect(0, 0, badge.getWidth(), badge.getHeight()), - new Rect(srcTgt.getWidth() - badgeSize, - srcTgt.getHeight() - badgeSize, srcTgt.getWidth(), srcTgt.getHeight()), - new Paint(Paint.FILTER_BITMAP_FLAG)); - sCanvas.setBitmap(null); - } - return srcTgt; - } - - /** - * Returns a bitmap suitable for the all apps view. - */ - public static Bitmap createIconBitmap(Drawable icon, Context context) { - return createIconBitmap(icon, context, 1.0f /* scale */); - } - - /** - * @param scale the scale to apply before drawing {@param icon} on the canvas - */ - public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) { - synchronized (sCanvas) { - final int iconBitmapSize = getIconBitmapSize(); - - int width = iconBitmapSize; - int height = iconBitmapSize; - - if (icon instanceof PaintDrawable) { - PaintDrawable painter = (PaintDrawable) icon; - painter.setIntrinsicWidth(width); - painter.setIntrinsicHeight(height); - } else if (icon instanceof BitmapDrawable) { - // Ensure the bitmap has a density. - BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; - Bitmap bitmap = bitmapDrawable.getBitmap(); - if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) { - bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics()); - } - } - int sourceWidth = icon.getIntrinsicWidth(); - int sourceHeight = icon.getIntrinsicHeight(); - if (sourceWidth > 0 && sourceHeight > 0) { - // Scale the icon proportionally to the icon dimensions - final float ratio = (float) sourceWidth / sourceHeight; - if (sourceWidth > sourceHeight) { - height = (int) (width / ratio); - } else if (sourceHeight > sourceWidth) { - width = (int) (height * ratio); - } - } - - // no intrinsic size --> use default size - int textureWidth = iconBitmapSize; - int textureHeight = iconBitmapSize; - - final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight, - Bitmap.Config.ARGB_8888); - final Canvas canvas = sCanvas; - canvas.setBitmap(bitmap); - - final int left = (textureWidth-width) / 2; - final int top = (textureHeight-height) / 2; - - @SuppressWarnings("all") // suppress dead code warning - final boolean debug = false; - if (debug) { - // draw a big box for the icon for debugging - canvas.drawColor(sColors[sColorIndex]); - if (++sColorIndex >= sColors.length) sColorIndex = 0; - Paint debugPaint = new Paint(); - debugPaint.setColor(0xffcccc00); - canvas.drawRect(left, top, left+width, top+height, debugPaint); - } - - sOldBounds.set(icon.getBounds()); - icon.setBounds(left, top, left+width, top+height); - canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2); - icon.draw(canvas); - canvas.restore(); - icon.setBounds(sOldBounds); - canvas.setBitmap(null); - - return bitmap; - } - } - /** * Given a coordinate relative to the descendant, find the coordinate in a parent view's * coordinates. @@ -880,28 +666,6 @@ public final class Utilities { return c == null || c.isEmpty(); } - /** - * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size. - * This allows the badging to be done based on the action bitmap size rather than - * the scaled bitmap size. - */ - private static class FixedSizeBitmapDrawable extends BitmapDrawable { - - public FixedSizeBitmapDrawable(Bitmap bitmap) { - super(null, bitmap); - } - - @Override - public int getIntrinsicHeight() { - return getBitmap().getWidth(); - } - - @Override - public int getIntrinsicWidth() { - return getBitmap().getWidth(); - } - } - public static int getColorAccent(Context context) { TypedArray ta = context.obtainStyledAttributes(new int[]{android.R.attr.colorAccent}); int colorAccent = ta.getColor(0, 0); diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index fc35064d0..315e23ad2 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -28,7 +28,6 @@ import android.annotation.SuppressLint; import android.app.WallpaperManager; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; -import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; @@ -74,6 +73,7 @@ import com.android.launcher3.dragndrop.SpringLoadedDragController; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.graphics.DragPreviewProvider; +import com.android.launcher3.shortcuts.DeepShortcutsContainer; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; import com.android.launcher3.util.ItemInfoMatcher; @@ -737,11 +737,7 @@ public class Workspace extends PagedView addFullScreenPage(customScreen); // Update the custom content hint - if (mRestorePage != INVALID_RESTORE_PAGE) { - mRestorePage = mRestorePage + 1; - } else { - setCurrentPage(getCurrentPage() + 1); - } + setCurrentPage(getCurrentPage() + 1); } public void removeCustomContentPage() { @@ -762,11 +758,7 @@ public class Workspace extends PagedView mCustomContentCallbacks = null; // Update the custom content hint - if (mRestorePage != INVALID_RESTORE_PAGE) { - mRestorePage = mRestorePage - 1; - } else { - setCurrentPage(getCurrentPage() - 1); - } + setCurrentPage(getCurrentPage() - 1); } public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks, @@ -1756,7 +1748,7 @@ public class Workspace extends PagedView } public boolean isOnOrMovingToCustomContent() { - return hasCustomContent() && getNextPage() == 0 && mRestorePage == INVALID_RESTORE_PAGE; + return hasCustomContent() && getNextPage() == 0; } private void updateStateForCustomContent(int screenCenter) { @@ -2009,7 +2001,7 @@ public class Workspace extends PagedView public void exitWidgetResizeMode() { DragLayer dragLayer = mLauncher.getDragLayer(); - dragLayer.clearAllResizeFrames(); + dragLayer.clearResizeFrame(); } @Override @@ -2348,6 +2340,13 @@ public class Workspace extends PagedView mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent(); } + if (child instanceof BubbleTextView) { + DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon((BubbleTextView) child); + if (dsc != null) { + dragOptions.preDragCondition = dsc.createPreDragCondition(); + } + } + DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, dragObject, dragVisualizeOffset, dragRect, scale, dragOptions); dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor()); @@ -2710,7 +2709,7 @@ public class Workspace extends PagedView public void run() { if (!isPageMoving() && !mIsSwitchingState) { DragLayer dragLayer = mLauncher.getDragLayer(); - dragLayer.addResizeFrame(info, hostView, cellLayout); + dragLayer.addResizeFrame(hostView, cellLayout); } } }; @@ -4000,63 +3999,34 @@ public class Workspace extends PagedView for (final CellLayout layoutParent: cellLayouts) { final ViewGroup layout = layoutParent.getShortcutsAndWidgets(); - final HashMap<ItemInfo, View> children = new HashMap<>(); + LongArrayMap<View> idToViewMap = new LongArrayMap<>(); + ArrayList<ItemInfo> items = new ArrayList<>(); for (int j = 0; j < layout.getChildCount(); j++) { final View view = layout.getChildAt(j); - children.put((ItemInfo) view.getTag(), view); - } - - final ArrayList<View> childrenToRemove = new ArrayList<>(); - final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove = new HashMap<>(); - LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() { - @Override - public boolean filterItem(ItemInfo parent, ItemInfo info, - ComponentName cn) { - if (parent instanceof FolderInfo) { - if (matcher.matches(info, cn)) { - FolderInfo folder = (FolderInfo) parent; - ArrayList<ShortcutInfo> appsToRemove; - if (folderAppsToRemove.containsKey(folder)) { - appsToRemove = folderAppsToRemove.get(folder); - } else { - appsToRemove = new ArrayList<ShortcutInfo>(); - folderAppsToRemove.put(folder, appsToRemove); - } - appsToRemove.add((ShortcutInfo) info); - return true; - } - } else { - if (matcher.matches(info, cn)) { - childrenToRemove.add(children.get(info)); - return true; - } - } - return false; - } - }; - LauncherModel.filterItemInfos(children.keySet(), filter); - - // Remove all the apps from their folders - for (FolderInfo folder : folderAppsToRemove.keySet()) { - ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder); - for (ShortcutInfo info : appsToRemove) { - folder.remove(info, false); + if (view.getTag() instanceof ItemInfo) { + ItemInfo item = (ItemInfo) view.getTag(); + items.add(item); + idToViewMap.put(item.id, view); } } - // Remove all the other children - for (View child : childrenToRemove) { - // Note: We can not remove the view directly from CellLayoutChildren as this - // does not re-mark the spaces as unoccupied. - layoutParent.removeViewInLayout(child); - if (child instanceof DropTarget) { - mDragController.removeDropTarget((DropTarget) child); - } - } + for (ItemInfo itemToRemove : matcher.filterItemInfos(items)) { + View child = idToViewMap.get(itemToRemove.id); - if (childrenToRemove.size() > 0) { - layout.requestLayout(); - layout.invalidate(); + if (child != null) { + // Note: We can not remove the view directly from CellLayoutChildren as this + // does not re-mark the spaces as unoccupied. + layoutParent.removeViewInLayout(child); + if (child instanceof DropTarget) { + mDragController.removeDropTarget((DropTarget) child); + } + } else if (itemToRemove.container >= 0) { + // The item may belong to a folder. + View parent = idToViewMap.get(itemToRemove.container); + if (parent != null) { + ((FolderInfo) parent.getTag()).remove((ShortcutInfo) itemToRemove, false); + } + } } } @@ -4157,8 +4127,9 @@ public class Workspace extends PagedView public void removeAbandonedPromise(String packageName, UserHandleCompat user) { HashSet<String> packages = new HashSet<>(1); packages.add(packageName); - LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user); - removeItemsByMatcher(ItemInfoMatcher.ofPackages(packages, user)); + ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packages, user); + LauncherModel.deleteItemsFromDatabase(mLauncher, matcher); + removeItemsByMatcher(matcher); } public void updateRestoreItems(final HashSet<ItemInfo> updates) { @@ -4335,7 +4306,6 @@ public class Workspace extends PagedView @Override public boolean evaluate(ItemInfo info, View view) { if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) { - PendingAppWidgetHostView hostView = (PendingAppWidgetHostView) view; mLauncher.removeItem(view, info, false /* deleteFromDb */); mLauncher.bindAppWidget((LauncherAppWidgetInfo) info); } diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 173aad044..439e3145e 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -57,7 +57,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme protected static final int MOVE = R.id.action_move; protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace; protected static final int RESIZE = R.id.action_resize; - protected static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts; + public static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts; public enum DragType { ICON, @@ -100,14 +100,17 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); - addActions(host, info); + addSupportedActions(host, info, false); } - protected void addActions(View host, AccessibilityNodeInfo info) { + public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) { if (!(host.getTag() instanceof ItemInfo)) return; ItemInfo item = (ItemInfo) host.getTag(); - if (host instanceof BubbleTextView && ((BubbleTextView) host).hasDeepShortcuts()) { + // If the request came from keyboard, do not add custom shortcuts as that is already + // exposed as a direct shortcut + if (!fromKeyboard && host instanceof BubbleTextView + && ((BubbleTextView) host).hasDeepShortcuts()) { info.addAction(mActions.get(DEEP_SHORTCUTS)); } @@ -121,9 +124,10 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme info.addAction(mActions.get(INFO)); } - if ((item instanceof ShortcutInfo) + // Do not add move actions for keyboard request as this uses virtual nodes. + if (!fromKeyboard && ((item instanceof ShortcutInfo) || (item instanceof LauncherAppWidgetInfo) - || (item instanceof FolderInfo)) { + || (item instanceof FolderInfo))) { info.addAction(mActions.get(MOVE)); if (item.container >= 0) { diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java index 0baa8f3db..5baa7b59e 100644 --- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java @@ -40,8 +40,10 @@ public class ShortcutMenuAccessibilityDelegate extends LauncherAccessibilityDele } @Override - protected void addActions(View host, AccessibilityNodeInfo info) { - info.addAction(mActions.get(ADD_TO_WORKSPACE)); + public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) { + if ((host.getParent() instanceof DeepShortcutView)) { + info.addAction(mActions.get(ADD_TO_WORKSPACE)); + } } @Override diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 5892787f3..77e8ad17d 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -15,10 +15,8 @@ */ package com.android.launcher3.allapps; -import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; -import android.graphics.Point; import android.graphics.Rect; import android.support.v7.widget.RecyclerView; import android.text.Selection; @@ -31,18 +29,17 @@ import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; -import android.view.ViewConfiguration; import android.view.ViewGroup; import com.android.launcher3.AppInfo; import com.android.launcher3.BaseContainerView; -import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; import com.android.launcher3.ExtendedEditText; +import com.android.launcher3.Insettable; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherTransitionable; @@ -50,11 +47,11 @@ 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.DragController; 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.shortcuts.DeepShortcutsContainer; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; import com.android.launcher3.util.ComponentKey; @@ -134,7 +131,8 @@ final class SimpleSectionMergeAlgorithm implements AlphabeticalAppsList.MergeAlg * The all apps view container. */ public class AllAppsContainerView extends BaseContainerView implements DragSource, - LauncherTransitionable, View.OnLongClickListener, AllAppsSearchBarController.Callbacks { + LauncherTransitionable, View.OnLongClickListener, AllAppsSearchBarController.Callbacks, + Insettable { private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3; private static final int MAX_NUM_MERGES_PHONE = 2; @@ -145,25 +143,18 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc private final RecyclerView.LayoutManager mLayoutManager; private final RecyclerView.ItemDecoration mItemDecoration; - // The computed bounds of the container - private final Rect mContentBounds = new Rect(); - private AllAppsRecyclerView mAppsRecyclerView; private AllAppsSearchBarController mSearchBarController; private View mSearchContainer; private ExtendedEditText mSearchInput; private HeaderElevationController mElevationController; - private int mSearchContainerOffsetTop; private SpannableStringBuilder mSearchQueryBuilder = null; private int mSectionNamesMargin; private int mNumAppsPerRow; private int mNumPredictedAppsPerRow; - private int mRecyclerViewBottomPadding; - // This coordinate is relative to this container view - private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1); public AllAppsContainerView(Context context) { this(context, null); @@ -184,14 +175,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mApps.setAdapter(mAdapter); mLayoutManager = mAdapter.getLayoutManager(); mItemDecoration = mAdapter.getItemDecoration(); - DeviceProfile grid = mLauncher.getDeviceProfile(); - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && !grid.isVerticalBarLayout()) { - mRecyclerViewBottomPadding = 0; - setPadding(0, 0, 0, 0); - } else { - mRecyclerViewBottomPadding = - res.getDimensionPixelSize(R.dimen.all_apps_list_bottom_padding); - } mSearchQueryBuilder = new SpannableStringBuilder(); Selection.setSelection(mSearchQueryBuilder, 0); } @@ -340,9 +323,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); mSearchInput.setHint(spanned); - mSearchContainerOffsetTop = getResources().getDimensionPixelSize( - R.dimen.all_apps_search_bar_margin_top); - mElevationController = Utilities.ATLEAST_LOLLIPOP ? new HeaderElevationController.ControllerVL(mSearchContainer) : new HeaderElevationController.ControllerV16(mSearchContainer); @@ -370,6 +350,19 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc getContentView().setVisibility(View.VISIBLE); getContentView().setBackground(null); } + + int maxScrollBarWidth = mAppsRecyclerView.getMaxScrollbarWidth(); + int startInset = Math.max(mSectionNamesMargin, maxScrollBarWidth); + if (Utilities.isRtl(getResources())) { + mAppsRecyclerView.setPadding(maxScrollBarWidth, 0, startInset, 0); + } else { + mAppsRecyclerView.setPadding(startInset, 0, maxScrollBarWidth, 0); + } + } + + @Override + public View getTouchDelegateTargetView() { + return mAppsRecyclerView; } @Override @@ -377,11 +370,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int widthPx = MeasureSpec.getSize(widthMeasureSpec); - int heightPx = MeasureSpec.getSize(heightMeasureSpec); - updatePaddingsAndMargins(widthPx, heightPx); - mContentBounds.set(mContainerPaddingLeft, 0, widthPx - mContainerPaddingRight, heightPx); - DeviceProfile grid = mLauncher.getDeviceProfile(); grid.updateAppsViewNumCols(); if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { @@ -393,17 +381,12 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow); mAdapter.setNumAppsPerRow(mNumAppsPerRow); mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, new FullMergeAlgorithm()); - if (mNumAppsPerRow > 0) { - int rvPadding = mAppsRecyclerView.getPaddingStart(); // Assumes symmetry - final int thumbMaxWidth = - getResources().getDimensionPixelSize( - R.dimen.container_fastscroll_thumb_max_width); - mSearchContainer.setPadding( - rvPadding - mContainerPaddingLeft + thumbMaxWidth, - mSearchContainer.getPaddingTop(), - rvPadding - mContainerPaddingRight + thumbMaxWidth, - mSearchContainer.getPaddingBottom()); - } + } + if (!grid.isVerticalBarLayout()) { + MarginLayoutParams searchContainerLp = + (MarginLayoutParams) mSearchContainer.getLayoutParams(); + searchContainerLp.height = grid.hotseatBarHeightPx; + mSearchContainer.setLayoutParams(searchContainerLp); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); return; @@ -437,73 +420,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc super.onMeasure(widthMeasureSpec, heightMeasureSpec); } - /** - * Update the background and padding of the Apps view and children. Instead of insetting the - * container view, we inset the background and padding of the recycler view to allow for the - * recycler view to handle touch events (for fast scrolling) all the way to the edge. - */ - private void updatePaddingsAndMargins(int widthPx, int heightPx) { - Rect bgPadding = new Rect(); - getRevealView().getBackground().getPadding(bgPadding); - - mAppsRecyclerView.updateBackgroundPadding(bgPadding); - mAdapter.updateBackgroundPadding(bgPadding); - mElevationController.updateBackgroundPadding(bgPadding); - - // Pad the recycler view by the background padding plus the start margin (for the section - // names) - int maxScrollBarWidth = mAppsRecyclerView.getMaxScrollbarWidth(); - int startInset = Math.max(mSectionNamesMargin, maxScrollBarWidth); - if (Utilities.isRtl(getResources())) { - mAppsRecyclerView.setPadding(bgPadding.left + maxScrollBarWidth, 0, bgPadding.right - + startInset, mRecyclerViewBottomPadding); - } else { - mAppsRecyclerView.setPadding(bgPadding.left + startInset, 0, bgPadding.right + - maxScrollBarWidth, mRecyclerViewBottomPadding); - } - - MarginLayoutParams lp = (MarginLayoutParams) mSearchContainer.getLayoutParams(); - lp.leftMargin = bgPadding.left; - lp.rightMargin = bgPadding.right; - - // Clip the view to the left and right edge of the background to - // to prevent shadows from rendering beyond the edges - final Rect newClipBounds = new Rect( - bgPadding.left, 0, widthPx - bgPadding.right, heightPx); - setClipBounds(newClipBounds); - - // Allow the overscroll effect to reach the edges of the view - mAppsRecyclerView.setClipToPadding(false); - - DeviceProfile grid = mLauncher.getDeviceProfile(); - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { - if (!grid.isVerticalBarLayout()) { - MarginLayoutParams mlp = (MarginLayoutParams) mAppsRecyclerView.getLayoutParams(); - - Rect insets = mLauncher.getDragLayer().getInsets(); - getContentView().setPadding(0, 0, 0, 0); - int height = insets.top + grid.hotseatCellHeightPx; - - mlp.topMargin = height; - mAppsRecyclerView.setLayoutParams(mlp); - - mSearchContainer.setPadding( - mSearchContainer.getPaddingLeft(), - insets.top + mSearchContainerOffsetTop, - mSearchContainer.getPaddingRight(), - mSearchContainer.getPaddingBottom()); - lp.height = height; - - View navBarBg = findViewById(R.id.nav_bar_bg); - ViewGroup.LayoutParams params = navBarBg.getLayoutParams(); - params.height = insets.bottom; - navBarBg.setLayoutParams(params); - navBarBg.setVisibility(View.VISIBLE); - } - } - mSearchContainer.setLayoutParams(lp); - } - @Override public boolean dispatchKeyEvent(KeyEvent event) { // Determine if the key event was actual text, if so, focus the search bar and then dispatch @@ -526,18 +442,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - return handleTouchEvent(ev); - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent ev) { - return handleTouchEvent(ev); - } - - @Override - public boolean onLongClick(View v) { + public boolean onLongClick(final View v) { // Return early if this is not initiated from a touch if (!v.isInTouchMode()) return false; // When we have exited all apps or are in transition, disregard long clicks @@ -549,22 +454,20 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc if (mLauncher.getDragController().isDragging()) return false; // Start the drag - DragOptions dragOptions = new DragOptions(); - if (v instanceof BubbleTextView) { - final BubbleTextView icon = (BubbleTextView) v; - if (icon.hasDeepShortcuts()) { - DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon); - if (dsc != null) { - dragOptions.deferDragCondition = dsc.createDeferDragCondition(new Runnable() { - @Override - public void run() { - icon.setVisibility(VISIBLE); - } - }); - } + final DragController dragController = mLauncher.getDragController(); + dragController.addDragListener(new DragController.DragListener() { + @Override + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { + v.setVisibility(INVISIBLE); } - } - mLauncher.getWorkspace().beginDragShared(v, this, dragOptions); + + @Override + public void onDragEnd() { + v.setVisibility(VISIBLE); + dragController.removeDragListener(this); + } + }); + mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions()); if (FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) { // Enter spring loaded mode (the new workspace does this in // onDragStart(), so we don't want to do it here) @@ -619,7 +522,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc // target layout we were dropping on. if (!success) { boolean showOutOfSpaceMessage = false; - if (target instanceof Workspace && !mLauncher.getDragController().isDeferringDrag()) { + if (target instanceof Workspace) { int currentScreen = mLauncher.getCurrentWorkspaceScreen(); Workspace workspace = (Workspace) target; CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); @@ -660,55 +563,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } } - /** - * Handles the touch events to dismiss all apps when clicking outside the bounds of the - * recycler view. - */ - private boolean handleTouchEvent(MotionEvent ev) { - DeviceProfile grid = mLauncher.getDeviceProfile(); - int x = (int) ev.getX(); - int y = (int) ev.getY(); - - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - if (!mContentBounds.isEmpty()) { - // Outset the fixed bounds and check if the touch is outside all apps - Rect tmpRect = new Rect(mContentBounds); - tmpRect.inset(-grid.allAppsIconSizePx / 2, 0); - if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) { - mBoundsCheckLastTouchDownPos.set(x, y); - return true; - } - } else { - // Check if the touch is outside all apps - if (ev.getX() < getPaddingLeft() || - ev.getX() > (getWidth() - getPaddingRight())) { - mBoundsCheckLastTouchDownPos.set(x, y); - return true; - } - } - break; - case MotionEvent.ACTION_UP: - if (mBoundsCheckLastTouchDownPos.x > -1) { - ViewConfiguration viewConfig = ViewConfiguration.get(getContext()); - float dx = ev.getX() - mBoundsCheckLastTouchDownPos.x; - float dy = ev.getY() - mBoundsCheckLastTouchDownPos.y; - float distance = (float) Math.hypot(dx, dy); - if (distance < viewConfig.getScaledTouchSlop()) { - // The background was clicked, so just go home - Launcher launcher = (Launcher) getContext(); - launcher.showWorkspace(true); - return true; - } - } - // Fall through - case MotionEvent.ACTION_CANCEL: - mBoundsCheckLastTouchDownPos.set(-1, -1); - break; - } - return false; - } - @Override public void onSearchResult(String query, ArrayList<ComponentKey> apps) { if (apps != null) { @@ -739,4 +593,22 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc public boolean shouldRestoreImeState() { return !TextUtils.isEmpty(mSearchInput.getText()); } + + @Override + public void setInsets(Rect insets) { + DeviceProfile grid = mLauncher.getDeviceProfile(); + if (grid.isVerticalBarLayout()) { + ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams(); + mlp.leftMargin = insets.left; + mlp.topMargin = insets.top; + mlp.rightMargin = insets.right; + setLayoutParams(mlp); + } else { + View navBarBg = findViewById(R.id.nav_bar_bg); + ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams(); + navBarBgLp.height = insets.bottom; + navBarBg.setLayoutParams(navBarBgLp); + navBarBg.setVisibility(View.VISIBLE); + } + } } diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java index 1d5b209c2..cac388c2b 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java @@ -21,6 +21,7 @@ import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import android.widget.RelativeLayout; import com.android.launcher3.BubbleTextView; import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler; @@ -33,7 +34,7 @@ import com.android.launcher3.R; * A container for RecyclerView to allow for the click shadow view to be shown behind an icon that * is launching. */ -public class AllAppsRecyclerViewContainerView extends FrameLayout +public class AllAppsRecyclerViewContainerView extends RelativeLayout implements BubbleTextShadowHandler { private final ClickShadowView mTouchFeedbackView; diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index b129bb09d..adfad0813 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -108,7 +108,7 @@ public class AllAppsTransitionController implements TouchController, VerticalPul } @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { mNoIntercept = false; if (!mLauncher.isAllAppsVisible() && mLauncher.getWorkspace().workspaceInModalState()) { @@ -174,7 +174,7 @@ public class AllAppsTransitionController implements TouchController, VerticalPul } @Override - public boolean onTouchEvent(MotionEvent ev) { + public boolean onControllerTouchEvent(MotionEvent ev) { return mDetector.onTouchEvent(ev); } diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index 6eb7dcc20..19a47e873 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -131,7 +131,7 @@ public class DragController implements DragDriver.EventListener, TouchController protected final int mFlingToDeleteThresholdVelocity; private VelocityTracker mVelocityTracker; - private boolean mIsDragDeferred; + private boolean mIsInPreDrag; /** * Interface to receive notifications when a drag starts or stops @@ -230,13 +230,14 @@ public class DragController implements DragDriver.EventListener, TouchController mDragObject = new DropTarget.DragObject(); - mIsDragDeferred = !mOptions.deferDragCondition.shouldStartDeferredDrag(0); + mIsInPreDrag = mOptions.preDragCondition != null + && !mOptions.preDragCondition.shouldStartDrag(0); final Resources res = mLauncher.getResources(); final float scaleDps = FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? res.getDimensionPixelSize(R.dimen.dragViewScale) - : mIsDragDeferred - ? res.getDimensionPixelSize(R.dimen.deferred_drag_view_scale) + : mIsInPreDrag + ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f; final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX, registrationY, initialDragViewScale, scaleDps); @@ -271,10 +272,10 @@ public class DragController implements DragDriver.EventListener, TouchController dragView.show(mMotionDownX, mMotionDownY); mDistanceSinceScroll = 0; - if (!mIsDragDeferred) { - startDeferredDrag(); - } else { - mOptions.deferDragCondition.onDeferredDragStart(); + if (!mIsInPreDrag) { + callOnDragStart(); + } else if (mOptions.preDragCondition != null) { + mOptions.preDragCondition.onPreDragStart(); } mLastTouch[0] = mMotionDownX; @@ -284,16 +285,18 @@ public class DragController implements DragDriver.EventListener, TouchController return dragView; } - public boolean isDeferringDrag() { - return mIsDragDeferred; - } - - public void startDeferredDrag() { + private void callOnDragStart() { for (DragListener listener : new ArrayList<>(mListeners)) { listener.onDragStart(mDragObject, mOptions); } - mOptions.deferDragCondition.onDragStart(); - mIsDragDeferred = false; + if (mOptions.preDragCondition != null) { + mOptions.preDragCondition.onPreDragEnd(true /* dragStarted*/); + } + mIsInPreDrag = false; + } + + public boolean isInPreDrag() { + return mIsInPreDrag; } /** @@ -329,7 +332,9 @@ public class DragController implements DragDriver.EventListener, TouchController mDragObject.deferDragViewCleanupPostAnimation = false; mDragObject.cancelled = true; mDragObject.dragComplete = true; - mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false); + if (!mIsInPreDrag) { + mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false); + } } endDrag(); } @@ -350,7 +355,6 @@ public class DragController implements DragDriver.EventListener, TouchController private void endDrag() { if (isDragging()) { mDragDriver = null; - mOptions = null; clearScrollRunnable(); boolean isDeferred = false; if (mDragObject.dragView != null) { @@ -363,15 +367,24 @@ public class DragController implements DragDriver.EventListener, TouchController // Only end the drag if we are not deferred if (!isDeferred) { - for (DragListener listener : new ArrayList<>(mListeners)) { - listener.onDragEnd(); - } + callOnDragEnd(); } } releaseVelocityTracker(); } + private void callOnDragEnd() { + if (mIsInPreDrag && mOptions.preDragCondition != null) { + mOptions.preDragCondition.onPreDragEnd(false /* dragStarted*/); + } + mIsInPreDrag = false; + mOptions = null; + for (DragListener listener : new ArrayList<>(mListeners)) { + listener.onDragEnd(); + } + } + /** * This only gets called as a result of drag view cleanup being deferred in endDrag(); */ @@ -380,9 +393,7 @@ public class DragController implements DragDriver.EventListener, TouchController if (mDragObject.deferDragViewCleanupPostAnimation) { // If we skipped calling onDragEnd() before, do it now - for (DragListener listener : new ArrayList<>(mListeners)) { - listener.onDragEnd(); - } + callOnDragEnd(); } } @@ -456,7 +467,7 @@ public class DragController implements DragDriver.EventListener, TouchController /** * Call this from a drag source view. */ - public boolean onInterceptTouchEvent(MotionEvent ev) { + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { if (mOptions != null && mOptions.isAccessibleDrag) { return false; } @@ -536,9 +547,9 @@ public class DragController implements DragDriver.EventListener, TouchController mLastTouch[1] = y; checkScrollState(x, y); - if (mIsDragDeferred && mOptions.deferDragCondition.shouldStartDeferredDrag( - Math.hypot(x - mMotionDownX, y - mMotionDownY))) { - startDeferredDrag(); + if (mIsInPreDrag && mOptions.preDragCondition != null + && mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) { + callOnDragStart(); } } @@ -604,7 +615,7 @@ public class DragController implements DragDriver.EventListener, TouchController /** * Call this from a drag source view. */ - public boolean onTouchEvent(MotionEvent ev) { + public boolean onControllerTouchEvent(MotionEvent ev) { if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) { return false; } @@ -701,7 +712,7 @@ public class DragController implements DragDriver.EventListener, TouchController (vec1.length() * vec2.length())); } - void drop(DropTarget dropTarget, float x, float y, PointF flingVel) { + private void drop(DropTarget dropTarget, float x, float y, PointF flingVel) { final int[] coordinates = mCoordinatesTemp; mDragObject.x = coordinates[0]; @@ -734,11 +745,15 @@ public class DragController implements DragDriver.EventListener, TouchController } } final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null; - mDragObject.dragSource.onDropCompleted( - dropTargetAsView, mDragObject, flingVel != null, accepted); mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView); - if (mIsDragDeferred) { - mOptions.deferDragCondition.onDropBeforeDeferredDrag(); + if (!mIsInPreDrag) { + mDragObject.dragSource.onDropCompleted( + dropTargetAsView, mDragObject, flingVel != null, accepted); + } else { + // Only defer the drag view cleanup if the drag source handles the drop. + if (!(mDragObject.dragSource instanceof DropTarget)) { + mDragObject.deferDragViewCleanupPostAnimation = false; + } } } diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index 016347b17..15ee4118d 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -36,7 +36,6 @@ 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; @@ -53,8 +52,6 @@ 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; @@ -72,7 +69,6 @@ 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; /** @@ -90,11 +86,9 @@ public class DragLayer extends InsettableFrameLayout { @Thunk DragController mDragController; - private int mXDown, mYDown; private Launcher mLauncher; // Variables relating to resizing widgets - private final ArrayList<AppWidgetResizeFrame> mResizeFrames = new ArrayList<>(); private final boolean mIsRtl; private AppWidgetResizeFrame mCurrentResizeFrame; @@ -209,23 +203,6 @@ public class DragLayer extends InsettableFrameLayout { } private boolean handleTouchDown(MotionEvent ev, boolean intercept) { - Rect hitRect = new Rect(); - int x = (int) ev.getX(); - int y = (int) ev.getY(); - - for (AppWidgetResizeFrame child: mResizeFrames) { - child.getHitRect(hitRect); - if (hitRect.contains(x, y)) { - if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) { - mCurrentResizeFrame = child; - mXDown = x; - mYDown = y; - requestDisallowInterceptTouchEvent(true); - return true; - } - } - } - // Remove the shortcuts container when touching outside of it. DeepShortcutsContainer deepShortcutsContainer = mLauncher.getOpenShortcutsContainer(); if (deepShortcutsContainer != null) { @@ -242,7 +219,7 @@ public class DragLayer extends InsettableFrameLayout { mLauncher.closeShortcutsContainer(); // We let touches on the original icon go through so that users can launch // the app with one tap if they don't find a shortcut they want. - return !isEventOverView(deepShortcutsContainer.getDeferredDragIcon(), ev); + return !isEventOverView(deepShortcutsContainer.getOriginalIcon(), ev); } } } @@ -289,21 +266,27 @@ public class DragLayer extends InsettableFrameLayout { } mTouchCompleteListener = null; } - clearAllResizeFrames(); - mActiveController = null; - if (mDragController.onInterceptTouchEvent(ev)) { + if (mCurrentResizeFrame != null + && mCurrentResizeFrame.onControllerInterceptTouchEvent(ev)) { + mActiveController = mCurrentResizeFrame; + return true; + } else { + clearResizeFrame(); + } + + if (mDragController.onControllerInterceptTouchEvent(ev)) { mActiveController = mDragController; return true; } - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onInterceptTouchEvent(ev)) { + if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onControllerInterceptTouchEvent(ev)) { mActiveController = mAllAppsController; return true; } - if (mPinchListener != null && mPinchListener.onInterceptTouchEvent(ev)) { + if (mPinchListener != null && mPinchListener.onControllerInterceptTouchEvent(ev)) { // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.) mActiveController = mPinchListener; return true; @@ -405,12 +388,8 @@ public class DragLayer extends InsettableFrameLayout { @Override public boolean onTouchEvent(MotionEvent ev) { - boolean handled = false; int action = ev.getAction(); - int x = (int) ev.getX(); - int y = (int) ev.getY(); - if (action == MotionEvent.ACTION_DOWN) { if (handleTouchDown(ev, false)) { return true; @@ -422,22 +401,8 @@ public class DragLayer extends InsettableFrameLayout { mTouchCompleteListener = null; } - if (mCurrentResizeFrame != null) { - handled = true; - switch (action) { - case MotionEvent.ACTION_MOVE: - mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); - mCurrentResizeFrame.onTouchUp(); - mCurrentResizeFrame = null; - } - } - if (handled) return true; if (mActiveController != null) { - return mActiveController.onTouchEvent(ev); + return mActiveController.onControllerTouchEvent(ev); } return false; } @@ -645,36 +610,24 @@ public class DragLayer extends InsettableFrameLayout { } } - public void clearAllResizeFrames() { - if (mResizeFrames.size() > 0) { - for (AppWidgetResizeFrame frame: mResizeFrames) { - frame.commitResize(); - removeView(frame); - } - mResizeFrames.clear(); + public void clearResizeFrame() { + if (mCurrentResizeFrame != null) { + mCurrentResizeFrame.commitResize(); + removeView(mCurrentResizeFrame); + mCurrentResizeFrame = null; } } - public boolean hasResizeFrames() { - return mResizeFrames.size() > 0; - } + public void addResizeFrame(LauncherAppWidgetHostView widget, CellLayout cellLayout) { + clearResizeFrame(); - public boolean isWidgetBeingResized() { - return mCurrentResizeFrame != null; - } - - public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, - CellLayout cellLayout) { - AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(), - widget, cellLayout, this); + mCurrentResizeFrame = new AppWidgetResizeFrame(getContext(), widget, cellLayout, this); LayoutParams lp = new LayoutParams(-1, -1); lp.customPosition = true; - addView(resizeFrame, lp); - mResizeFrames.add(resizeFrame); - - resizeFrame.snapToWidget(false); + addView(mCurrentResizeFrame, lp); + mCurrentResizeFrame.snapToWidget(false); } public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java index dbf46f338..906855a7c 100644 --- a/src/com/android/launcher3/dragndrop/DragOptions.java +++ b/src/com/android/launcher3/dragndrop/DragOptions.java @@ -17,6 +17,8 @@ package com.android.launcher3.dragndrop; import android.graphics.Point; +import android.support.annotation.CallSuper; +import android.view.View; /** * Set of options to control the drag and drop behavior. @@ -29,8 +31,8 @@ public class DragOptions { /** Specifies the start location for the system DnD, null when using internal DnD */ public Point systemDndStartPoint = null; - /** Determines when a deferred drag should start. By default, drags aren't deferred at all. */ - public DeferDragCondition deferDragCondition = new DeferDragCondition(); + /** Determines when a pre-drag should transition to a drag. By default, this is immediate. */ + public PreDragCondition preDragCondition = null; /** * Specifies a condition that must be met before DragListener#onDragStart() is called. @@ -38,34 +40,26 @@ public class DragOptions { * DragController#startDrag(). * * This condition can be overridden, and callbacks are provided for the following cases: - * - The drag starts, but onDragStart() is deferred (onDeferredDragStart()). - * - The drag ends before the condition is met (onDropBeforeDeferredDrag()). - * - The condition is met (onDragStart()). + * - The pre-drag starts, but onDragStart() is deferred (onPreDragStart()). + * - The pre-drag ends before the condition is met (onPreDragEnd(false)). + * - The actual drag starts when the condition is met (onPreDragEnd(true)). */ - public static class DeferDragCondition { - public boolean shouldStartDeferredDrag(double distanceDragged) { - return true; - } + public interface PreDragCondition { + + public boolean shouldStartDrag(double distanceDragged); /** - * The drag has started, but onDragStart() is deferred. - * This happens when shouldStartDeferredDrag() returns true. + * The pre-drag has started, but onDragStart() is + * deferred until shouldStartDrag() returns true. */ - public void onDeferredDragStart() { - // Do nothing. - } + void onPreDragStart(); /** - * User dropped before the deferred condition was met, - * i.e. before shouldStartDeferredDrag() returned true. + * The pre-drag has ended. This gets called at the same time as onDragStart() + * if the condition is met, otherwise at the same time as onDragEnd(). + * @param dragStarted Whether the pre-drag ended because the actual drag started. + * This will be true if the condition was met, otherwise false. */ - public void onDropBeforeDeferredDrag() { - // Do nothing - } - - /** onDragStart() has been called, now we are in a normal drag. */ - public void onDragStart() { - // Do nothing - } + void onPreDragEnd(boolean dragStarted); } } diff --git a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java index 6b14be714..ac50332bf 100644 --- a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java +++ b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java @@ -23,10 +23,10 @@ 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; +import com.android.launcher3.graphics.HolographicOutlineHelper; /** * Extension of {@link DragPreviewProvider} which provides a dummy outline when drag starts from @@ -72,7 +72,7 @@ public class ExternalDragPreviewProvider extends DragPreviewProvider { canvas.drawCircle(DRAG_BITMAP_PADDING / 2 + radius, DRAG_BITMAP_PADDING / 2 + radius, radius * 0.9f, paint); - HolographicOutlineHelper.obtain(mLauncher).applyExpensiveOutlineWithBlur(b, canvas); + HolographicOutlineHelper.getInstance(mLauncher).applyExpensiveOutlineWithBlur(b, canvas); canvas.setBitmap(null); return b; } diff --git a/src/com/android/launcher3/dynamicui/ExtractionUtils.java b/src/com/android/launcher3/dynamicui/ExtractionUtils.java index 6dc0035ee..1e663a9ae 100644 --- a/src/com/android/launcher3/dynamicui/ExtractionUtils.java +++ b/src/com/android/launcher3/dynamicui/ExtractionUtils.java @@ -16,18 +16,18 @@ package com.android.launcher3.dynamicui; +import android.annotation.TargetApi; import android.app.WallpaperManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Color; +import android.os.Build; import android.support.v4.graphics.ColorUtils; import android.support.v7.graphics.Palette; import com.android.launcher3.Utilities; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.List; /** @@ -37,7 +37,6 @@ public class ExtractionUtils { public static final String EXTRACTED_COLORS_PREFERENCE_KEY = "pref_extractedColors"; public static final String WALLPAPER_ID_PREFERENCE_KEY = "pref_wallpaperId"; - private static final int FLAG_SET_SYSTEM = 1 << 0; // TODO: use WallpaperManager.FLAG_SET_SYSTEM private static final float MIN_CONTRAST_RATIO = 2f; /** @@ -73,14 +72,10 @@ public class ExtractionUtils { return wallpaperId != savedWallpaperId; } + @TargetApi(Build.VERSION_CODES.N) public static int getWallpaperId(WallpaperManager wallpaperManager) { - // TODO: use WallpaperManager#getWallpaperId(WallpaperManager.FLAG_SET_SYSTEM) directly. - try { - Method getWallpaperId = WallpaperManager.class.getMethod("getWallpaperId", int.class); - return (int) getWallpaperId.invoke(wallpaperManager, FLAG_SET_SYSTEM); - } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { - return -1; - } + return Utilities.isNycOrAbove() ? + wallpaperManager.getWallpaperId(WallpaperManager.FLAG_SYSTEM) : -1; } public static boolean isSuperLight(Palette p) { diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index ebbe64127..4fe351368 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -281,17 +281,7 @@ 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; - DragOptions dragOptions = new DragOptions(); - if (v instanceof BubbleTextView) { - BubbleTextView icon = (BubbleTextView) v; - if (icon.hasDeepShortcuts()) { - DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon); - if (dsc != null) { - dragOptions.deferDragCondition = dsc.createDeferDragCondition(null); - } - } - } - return startDrag(v, dragOptions); + return startDrag(v, new DragOptions()); } public boolean startDrag(View v, DragOptions options) { @@ -916,7 +906,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) { replaceFolderWithFinalItem(); } - } else if (!mDragController.isDeferringDrag()) { + } else { // The drag failed, we need to return the item to the folder ShortcutInfo info = (ShortcutInfo) d.dragInfo; View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info) @@ -1309,7 +1299,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mIsExternalDrag = false; } else { currentDragView = mCurrentDragView; - if (!mDragController.isDeferringDrag()) { + // The view was never removed from this folder if we are still in the pre-drag. + if (!mDragController.isInPreDrag()) { mContent.addViewForRank(currentDragView, si, mEmptyCellRank); } } @@ -1332,7 +1323,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mItemsInvalidated = true; rearrangeChildren(); - if (!mDragController.isDeferringDrag()) { + // The ShortcutInfo was never removed if we are still in the pre-drag. + if (!mDragController.isInPreDrag()) { // Temporarily suppress the listener, as we did all the work already here. try (SuppressInfoChanges s = new SuppressInfoChanges()) { mInfo.add(si, false); diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java index bc91c15be..7ad1e3a43 100644 --- a/src/com/android/launcher3/graphics/DragPreviewProvider.java +++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java @@ -24,7 +24,6 @@ import android.graphics.drawable.Drawable; import android.view.View; import android.widget.TextView; -import com.android.launcher3.HolographicOutlineHelper; import com.android.launcher3.Launcher; import com.android.launcher3.PreloadIconDrawable; import com.android.launcher3.Workspace; @@ -137,7 +136,7 @@ public class DragPreviewProvider { mView.getHeight() + DRAG_BITMAP_PADDING, Bitmap.Config.ALPHA_8); canvas.setBitmap(b); drawDragView(canvas); - HolographicOutlineHelper.obtain(mView.getContext()) + HolographicOutlineHelper.getInstance(mView.getContext()) .applyExpensiveOutlineWithBlur(b, canvas); canvas.setBitmap(null); return b; diff --git a/src/com/android/launcher3/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java index 9dec7d9e4..0d70bdee1 100644 --- a/src/com/android/launcher3/HolographicOutlineHelper.java +++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.graphics; import android.content.Context; import android.content.res.Resources; @@ -29,6 +29,8 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.SparseArray; +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.R; import com.android.launcher3.config.ProviderConfig; import java.nio.ByteBuffer; @@ -72,9 +74,9 @@ public class HolographicOutlineHelper { mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); } - public static HolographicOutlineHelper obtain(Context context) { + public static HolographicOutlineHelper getInstance(Context context) { if (sInstance == null) { - sInstance = new HolographicOutlineHelper(context); + sInstance = new HolographicOutlineHelper(context.getApplicationContext()); } return sInstance; } @@ -155,11 +157,11 @@ public class HolographicOutlineHelper { thickInnerBlur.recycle(); } - Bitmap createMediumDropShadow(BubbleTextView view) { + public Bitmap createMediumDropShadow(BubbleTextView view) { return createMediumDropShadow(view.getIcon(), view.getScaleX(), view.getScaleY(), true); } - Bitmap createMediumDropShadow(Drawable drawable, boolean shouldCache) { + public Bitmap createMediumDropShadow(Drawable drawable, boolean shouldCache) { return createMediumDropShadow(drawable, 1f, 1f, shouldCache); } diff --git a/src/com/android/launcher3/util/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java index 040a1b51a..f90b2d72e 100644 --- a/src/com/android/launcher3/util/IconNormalizer.java +++ b/src/com/android/launcher3/graphics/IconNormalizer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3.util; +package com.android.launcher3.graphics; import android.graphics.Bitmap; import android.graphics.Canvas; diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java new file mode 100644 index 000000000..9f3f3571d --- /dev/null +++ b/src/com/android/launcher3/graphics/LauncherIcons.java @@ -0,0 +1,258 @@ +/* + * 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.graphics; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PaintFlagsDrawFilter; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.PaintDrawable; +import android.os.Build; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.config.FeatureFlags; + +/** + * Helper methods for generating various launcher icons + */ +public class LauncherIcons { + + private static final Rect sOldBounds = new Rect(); + private static final Canvas sCanvas = new Canvas(); + + static { + sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, + Paint.FILTER_BITMAP_FLAG)); + } + + + public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) { + byte[] data = c.getBlob(iconIndex); + try { + return createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), context); + } catch (Exception e) { + return null; + } + } + + /** + * Returns a bitmap suitable for the all apps view. If the package or the resource do not + * exist, it returns null. + */ + public static Bitmap createIconBitmap(String packageName, String resourceName, + Context context) { + PackageManager packageManager = context.getPackageManager(); + // the resource + try { + Resources resources = packageManager.getResourcesForApplication(packageName); + if (resources != null) { + final int id = resources.getIdentifier(resourceName, null, null); + return createIconBitmap( + resources.getDrawableForDensity(id, LauncherAppState.getInstance() + .getInvariantDeviceProfile().fillResIconDpi), context); + } + } catch (Exception e) { + // Icon not found. + } + return null; + } + + private static int getIconBitmapSize() { + return LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize; + } + + /** + * Returns a bitmap which is of the appropriate size to be displayed as an icon + */ + public static Bitmap createIconBitmap(Bitmap icon, Context context) { + final int iconBitmapSize = getIconBitmapSize(); + if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) { + return icon; + } + return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context); + } + + /** + * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}. + * The bitmap is also visually normalized with other icons. + */ + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public static Bitmap createBadgedIconBitmap( + Drawable icon, UserHandleCompat user, Context context) { + float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? + 1 : IconNormalizer.getInstance().getScale(icon, null); + Bitmap bitmap = createIconBitmap(icon, context, scale); + return badgeIconForUser(bitmap, user, context); + } + + /** + * Badges the provided icon with the user badge if required. + */ + public static Bitmap badgeIconForUser(Bitmap icon, UserHandleCompat user, Context context) { + if (Utilities.ATLEAST_LOLLIPOP && user != null + && !UserHandleCompat.myUserHandle().equals(user)) { + BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon); + Drawable badged = context.getPackageManager().getUserBadgedIcon( + drawable, user.getUser()); + if (badged instanceof BitmapDrawable) { + return ((BitmapDrawable) badged).getBitmap(); + } else { + return createIconBitmap(badged, context); + } + } else { + return icon; + } + } + + /** + * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually + * normalized with other icons and has enough spacing to add shadow. + */ + public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context) { + RectF iconBounds = new RectF(); + float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? + 1 : IconNormalizer.getInstance().getScale(icon, iconBounds); + scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds)); + return createIconBitmap(icon, context, scale); + } + + /** + * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using + * {@link #createScaledBitmapWithoutShadow(Drawable, Context)} + */ + public static Bitmap addShadowToIcon(Bitmap icon) { + return ShadowGenerator.getInstance().recreateIcon(icon); + } + + /** + * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions. + */ + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) { + int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size); + synchronized (sCanvas) { + sCanvas.setBitmap(srcTgt); + sCanvas.drawBitmap(badge, new Rect(0, 0, badge.getWidth(), badge.getHeight()), + new Rect(srcTgt.getWidth() - badgeSize, + srcTgt.getHeight() - badgeSize, srcTgt.getWidth(), srcTgt.getHeight()), + new Paint(Paint.FILTER_BITMAP_FLAG)); + sCanvas.setBitmap(null); + } + return srcTgt; + } + + /** + * Returns a bitmap suitable for the all apps view. + */ + public static Bitmap createIconBitmap(Drawable icon, Context context) { + return createIconBitmap(icon, context, 1.0f /* scale */); + } + + /** + * @param scale the scale to apply before drawing {@param icon} on the canvas + */ + public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) { + synchronized (sCanvas) { + final int iconBitmapSize = getIconBitmapSize(); + + int width = iconBitmapSize; + int height = iconBitmapSize; + + if (icon instanceof PaintDrawable) { + PaintDrawable painter = (PaintDrawable) icon; + painter.setIntrinsicWidth(width); + painter.setIntrinsicHeight(height); + } else if (icon instanceof BitmapDrawable) { + // Ensure the bitmap has a density. + BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; + Bitmap bitmap = bitmapDrawable.getBitmap(); + if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) { + bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics()); + } + } + int sourceWidth = icon.getIntrinsicWidth(); + int sourceHeight = icon.getIntrinsicHeight(); + if (sourceWidth > 0 && sourceHeight > 0) { + // Scale the icon proportionally to the icon dimensions + final float ratio = (float) sourceWidth / sourceHeight; + if (sourceWidth > sourceHeight) { + height = (int) (width / ratio); + } else if (sourceHeight > sourceWidth) { + width = (int) (height * ratio); + } + } + + // no intrinsic size --> use default size + int textureWidth = iconBitmapSize; + int textureHeight = iconBitmapSize; + + final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight, + Bitmap.Config.ARGB_8888); + final Canvas canvas = sCanvas; + canvas.setBitmap(bitmap); + + final int left = (textureWidth-width) / 2; + final int top = (textureHeight-height) / 2; + + sOldBounds.set(icon.getBounds()); + icon.setBounds(left, top, left+width, top+height); + canvas.save(Canvas.MATRIX_SAVE_FLAG); + canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2); + icon.draw(canvas); + canvas.restore(); + icon.setBounds(sOldBounds); + canvas.setBitmap(null); + + return bitmap; + } + } + + /** + * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size. + * This allows the badging to be done based on the action bitmap size rather than + * the scaled bitmap size. + */ + private static class FixedSizeBitmapDrawable extends BitmapDrawable { + + public FixedSizeBitmapDrawable(Bitmap bitmap) { + super(null, bitmap); + } + + @Override + public int getIntrinsicHeight() { + return getBitmap().getWidth(); + } + + @Override + public int getIntrinsicWidth() { + return getBitmap().getWidth(); + } + } +} diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java new file mode 100644 index 000000000..6056f4c10 --- /dev/null +++ b/src/com/android/launcher3/keyboard/CustomActionsPopup.java @@ -0,0 +1,93 @@ +/* + * 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.keyboard; + +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; +import android.widget.PopupMenu; +import android.widget.PopupMenu.OnMenuItemClickListener; + +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; +import com.android.launcher3.shortcuts.DeepShortcutsContainer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Handles showing a popup menu with available custom actions for a launcher icon. + * This allows exposing various custom actions using keyboard shortcuts. + */ +public class CustomActionsPopup implements OnMenuItemClickListener { + + private final Launcher mLauncher; + private final LauncherAccessibilityDelegate mDelegate; + private final View mIcon; + + public CustomActionsPopup(Launcher launcher, View icon) { + mLauncher = launcher; + mIcon = icon; + DeepShortcutsContainer container = launcher.getOpenShortcutsContainer(); + if (container != null) { + mDelegate = container.getAccessibilityDelegate(); + } else { + mDelegate = launcher.getAccessibilityDelegate(); + } + } + + private List<AccessibilityAction> getActionList() { + if (mIcon == null || !(mIcon.getTag() instanceof ItemInfo)) { + return Collections.EMPTY_LIST; + } + + AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); + mDelegate.addSupportedActions(mIcon, info, true); + List<AccessibilityAction> result = new ArrayList<>(info.getActionList()); + info.recycle(); + return result; + } + + public boolean canShow() { + return !getActionList().isEmpty(); + } + + public boolean show() { + List<AccessibilityAction> actions = getActionList(); + if (actions.isEmpty()) { + return false; + } + + PopupMenu popup = new PopupMenu(mLauncher, mIcon); + popup.setOnMenuItemClickListener(this); + Menu menu = popup.getMenu(); + for (AccessibilityAction action : actions) { + menu.add(Menu.NONE, action.getId(), Menu.NONE, action.getLabel()); + } + popup.show(); + return true; + } + + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + return mDelegate.performAction(mIcon, (ItemInfo) mIcon.getTag(), menuItem.getItemId()); + } +} diff --git a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java index 7672f5a79..b0d6b2dbf 100644 --- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java +++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java @@ -143,7 +143,7 @@ public abstract class FocusIndicatorHelper implements } private Rect getDrawRect() { - if (mCurrentView != null) { + if (mCurrentView != null && mCurrentView.isAttachedToWindow()) { viewToRect(mCurrentView, sTempRect1); if (mShift > 0 && mTargetView != null) { diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java new file mode 100644 index 000000000..2b7039928 --- /dev/null +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -0,0 +1,197 @@ +/* + * 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.model; + +import android.util.Log; +import android.util.MutableInt; + +import com.android.launcher3.FolderInfo; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppWidgetInfo; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.util.LongArrayMap; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * All the data stored in-memory and managed by the LauncherModel + */ +public class BgDataModel { + + private static final String TAG = "BgDataModel"; + + /** + * Map of all the ItemInfos (shortcuts, folders, and widgets) created by + * LauncherModel to their ids + */ + public final LongArrayMap<ItemInfo> itemsIdMap = new LongArrayMap<>(); + + /** + * List of all the folders and shortcuts directly on the home screen (no widgets + * or shortcuts within folders). + */ + public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>(); + + /** + * All LauncherAppWidgetInfo created by LauncherModel. + */ + public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); + + /** + * Map of id to FolderInfos of all the folders created by LauncherModel + */ + public final LongArrayMap<FolderInfo> folders = new LongArrayMap<>(); + + /** + * Ordered list of workspace screens ids. + */ + public final ArrayList<Long> workspaceScreens = new ArrayList<>(); + + /** + * Map of ShortcutKey to the number of times it is pinned. + */ + public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>(); + + /** + * Clears all the data + */ + public synchronized void clear() { + workspaceItems.clear(); + appWidgets.clear(); + folders.clear(); + itemsIdMap.clear(); + workspaceScreens.clear(); + pinnedShortcutCounts.clear(); + } + + public synchronized void removeItem(ItemInfo... items) { + removeItem(Arrays.asList(items)); + } + + public synchronized void removeItem(Iterable<? extends ItemInfo> items) { + for (ItemInfo item : items) { + switch (item.itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + folders.remove(item.id); + if (ProviderConfig.IS_DOGFOOD_BUILD) { + for (ItemInfo info : itemsIdMap) { + if (info.container == item.id) { + // We are deleting a folder which still contains items that + // think they are contained by that folder. + String msg = "deleting a folder (" + item + ") which still " + + "contains items (" + info + ")"; + Log.e(TAG, msg); + } + } + } + workspaceItems.remove(item); + break; + case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { + // Decrement pinned shortcut count + ShortcutKey pinnedShortcut = ShortcutKey.fromShortcutInfo((ShortcutInfo) item); + MutableInt count = pinnedShortcutCounts.get(pinnedShortcut); + if (count == null || --count.value == 0) { + LauncherAppState.getInstance() + .getShortcutManager().unpinShortcut(pinnedShortcut); + } + // Fall through. + } + case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + workspaceItems.remove(item); + break; + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: + appWidgets.remove(item); + break; + } + itemsIdMap.remove(item.id); + } + } + + public synchronized void addItem(ItemInfo item, boolean newItem) { + itemsIdMap.put(item.id, item); + switch (item.itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + folders.put(item.id, (FolderInfo) item); + workspaceItems.add(item); + break; + case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { + // Increment the count for the given shortcut + ShortcutKey pinnedShortcut = ShortcutKey.fromShortcutInfo((ShortcutInfo) item); + MutableInt count = pinnedShortcutCounts.get(pinnedShortcut); + if (count == null) { + count = new MutableInt(1); + pinnedShortcutCounts.put(pinnedShortcut, count); + } else { + count.value++; + } + + // Since this is a new item, pin the shortcut in the system server. + if (newItem && count.value == 1) { + LauncherAppState.getInstance().getShortcutManager() + .pinShortcut(pinnedShortcut); + } + // Fall through + } + case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || + item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + workspaceItems.add(item); + } else { + if (newItem) { + if (!folders.containsKey(item.container)) { + // Adding an item to a folder that doesn't exist. + String msg = "adding item: " + item + " to a folder that " + + " doesn't exist"; + Log.e(TAG, msg); + } + } else { + findOrMakeFolder(item.container).add((ShortcutInfo) item, false); + } + + } + break; + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: + appWidgets.add((LauncherAppWidgetInfo) item); + break; + } + } + + /** + * Return an existing FolderInfo object if we have encountered this ID previously, + * or make a new one. + */ + public synchronized FolderInfo findOrMakeFolder(long id) { + // See if a placeholder was created for us already + FolderInfo folderInfo = folders.get(id); + if (folderInfo == null) { + // No placeholder -- create a new instance + folderInfo = new FolderInfo(); + folders.put(id, folderInfo); + } + return folderInfo; + } +} diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java new file mode 100644 index 000000000..54260c915 --- /dev/null +++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.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.model; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.util.MultiHashMap; +import com.android.launcher3.util.PackageManagerHelper; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Map.Entry; + +/** + * Helper class to re-query app status when SD-card becomes available. + * + * During first load, just after reboot, some apps on sdcard might not be available immediately due + * to some race conditions in the system. We wait for ACTION_BOOT_COMPLETED and process such + * apps again. + */ +public class SdCardAvailableReceiver extends BroadcastReceiver { + + private final LauncherModel mModel; + private final Context mContext; + private final MultiHashMap<UserHandleCompat, String> mPackages; + + public SdCardAvailableReceiver(LauncherModel model, Context context, + MultiHashMap<UserHandleCompat, String> packages) { + mModel = model; + mContext = context; + mPackages = packages; + } + + @Override + public void onReceive(Context context, Intent intent) { + final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); + final PackageManager manager = context.getPackageManager(); + for (Entry<UserHandleCompat, ArrayList<String>> entry : mPackages.entrySet()) { + UserHandleCompat user = entry.getKey(); + + final ArrayList<String> packagesRemoved = new ArrayList<>(); + final ArrayList<String> packagesUnavailable = new ArrayList<>(); + + for (String pkg : new HashSet<>(entry.getValue())) { + if (!launcherApps.isPackageEnabledForProfile(pkg, user)) { + if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) { + packagesUnavailable.add(pkg); + } else { + packagesRemoved.add(pkg); + } + } + } + if (!packagesRemoved.isEmpty()) { + mModel.onPackagesRemoved(user, + packagesRemoved.toArray(new String[packagesRemoved.size()])); + } + if (!packagesUnavailable.isEmpty()) { + mModel.onPackagesUnavailable( + packagesUnavailable.toArray(new String[packagesUnavailable.size()]), + user, false); + } + } + + // Unregister the broadcast receiver, just in case + mContext.unregisterReceiver(this); + } +} diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java index 49d6fa932..9037af4d2 100644 --- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java +++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java @@ -42,13 +42,8 @@ import java.util.List; public class DeepShortcutManager { private static final String TAG = "DeepShortcutManager"; - // TODO: Replace this with platform constants when the new sdk is available. - public static final int FLAG_MATCH_DYNAMIC = 1 << 0; - public static final int FLAG_MATCH_MANIFEST = 1 << 3; - public static final int FLAG_MATCH_PINNED = 1 << 1; - - private static final int FLAG_GET_ALL = - FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST; + private static final int FLAG_GET_ALL = ShortcutQuery.FLAG_MATCH_DYNAMIC + | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED; private final LauncherApps mLauncherApps; private boolean mWasLastCallSuccess; @@ -86,7 +81,7 @@ public class DeepShortcutManager { */ public List<ShortcutInfoCompat> queryForShortcutsContainer(ComponentName activity, List<String> ids, UserHandleCompat user) { - return query(FLAG_MATCH_MANIFEST | FLAG_MATCH_DYNAMIC, + return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC, activity.getPackageName(), activity, ids, user); } @@ -172,7 +167,7 @@ public class DeepShortcutManager { */ public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName, UserHandleCompat user) { - return query(FLAG_MATCH_PINNED, packageName, null, null, user); + return query(ShortcutQuery.FLAG_MATCH_PINNED, packageName, null, null, user); } public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandleCompat user) { diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java index 2702d4e8e..31f382370 100644 --- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java +++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java @@ -85,7 +85,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC private final ShortcutMenuAccessibilityDelegate mAccessibilityDelegate; private final boolean mIsRtl; - private BubbleTextView mDeferredDragIcon; + private BubbleTextView mOriginalIcon; private final Rect mTempRect = new Rect(); private Point mIconLastTouchPos = new Point(); private boolean mIsLeftAligned; @@ -150,7 +150,8 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC animateOpen(); - deferDrag(originalIcon); + mOriginalIcon = originalIcon; + mLauncher.getDragController().addDragListener(this); // Load the shortcuts on a background thread and update the container as it animates. final Looper workerLooper = LauncherModel.getWorkerLooper(); @@ -375,13 +376,8 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC return arrowView; } - private void deferDrag(BubbleTextView originalIcon) { - mDeferredDragIcon = originalIcon; - mLauncher.getDragController().addDragListener(this); - } - - public BubbleTextView getDeferredDragIcon() { - return mDeferredDragIcon; + public BubbleTextView getOriginalIcon() { + return mOriginalIcon; } /** @@ -390,30 +386,26 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC * Current behavior: * - Start the drag if the touch passes a certain distance from the original touch down. */ - public DragOptions.DeferDragCondition createDeferDragCondition(final Runnable onDragStart) { - return new DragOptions.DeferDragCondition() { + public DragOptions.PreDragCondition createPreDragCondition() { + return new DragOptions.PreDragCondition() { @Override - public boolean shouldStartDeferredDrag(double distanceDragged) { + public boolean shouldStartDrag(double distanceDragged) { return distanceDragged > mStartDragThreshold; } @Override - public void onDeferredDragStart() { - mDeferredDragIcon.setVisibility(INVISIBLE); - } - - @Override - public void onDropBeforeDeferredDrag() { - mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mDeferredDragIcon); - if (!mIsAboveIcon) { - mDeferredDragIcon.setTextVisibility(false); - } + public void onPreDragStart() { + mOriginalIcon.setVisibility(INVISIBLE); } @Override - public void onDragStart() { - if (onDragStart != null) { - onDragStart.run(); + public void onPreDragEnd(boolean dragStarted) { + if (!dragStarted) { + mOriginalIcon.setVisibility(VISIBLE); + mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mOriginalIcon); + if (!mIsAboveIcon) { + mOriginalIcon.setTextVisibility(false); + } } } }; @@ -511,7 +503,6 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC } } } - mDeferredDragIcon.setVisibility(VISIBLE); } @Override @@ -609,6 +600,10 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC shortcutAnims.start(); } + public ShortcutMenuAccessibilityDelegate getAccessibilityDelegate() { + return mAccessibilityDelegate; + } + /** * Closes the folder without animation. */ @@ -619,9 +614,9 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC } mIsOpen = false; mDeferContainerRemoval = false; - boolean isInHotseat = ((ItemInfo) mDeferredDragIcon.getTag()).container + boolean isInHotseat = ((ItemInfo) mOriginalIcon.getTag()).container == LauncherSettings.Favorites.CONTAINER_HOTSEAT; - mDeferredDragIcon.setTextVisibility(!isInHotseat); + mOriginalIcon.setTextVisibility(!isInHotseat); mLauncher.getDragController().removeDragListener(this); mLauncher.getDragLayer().removeView(this); } @@ -643,7 +638,6 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC } List<String> ids = launcher.getShortcutIdsForItem((ItemInfo) icon.getTag()); if (!ids.isEmpty()) { - // There are shortcuts associated with the app, so defer its drag. final DeepShortcutsContainer container = (DeepShortcutsContainer) launcher.getLayoutInflater().inflate( R.layout.deep_shortcuts_container, launcher.getDragLayer(), false); diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java index 2adb82e2d..fc474f527 100644 --- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java +++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java @@ -23,7 +23,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.View; -import com.android.launcher3.HolographicOutlineHelper; +import com.android.launcher3.graphics.HolographicOutlineHelper; import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; import com.android.launcher3.graphics.DragPreviewProvider; @@ -44,7 +44,7 @@ public class ShortcutDragPreviewProvider extends DragPreviewProvider { public Bitmap createDragOutline(Canvas canvas) { Bitmap b = drawScaledPreview(canvas, Bitmap.Config.ALPHA_8); - HolographicOutlineHelper.obtain(mView.getContext()) + HolographicOutlineHelper.getInstance(mView.getContext()) .applyExpensiveOutlineWithBlur(b, canvas); canvas.setBitmap(null); return b; diff --git a/src/com/android/launcher3/util/CursorIconInfo.java b/src/com/android/launcher3/util/CursorIconInfo.java index 4fefa986e..6603ee765 100644 --- a/src/com/android/launcher3/util/CursorIconInfo.java +++ b/src/com/android/launcher3/util/CursorIconInfo.java @@ -25,6 +25,7 @@ import android.text.TextUtils; import com.android.launcher3.LauncherSettings; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; +import com.android.launcher3.graphics.LauncherIcons; /** * Utility class to load icon from a cursor. @@ -59,7 +60,7 @@ public class CursorIconInfo { info.iconResource = new ShortcutIconResource(); info.iconResource.packageName = packageName; info.iconResource.resourceName = resourceName; - icon = Utilities.createIconBitmap(packageName, resourceName, mContext); + icon = LauncherIcons.createIconBitmap(packageName, resourceName, mContext); } if (icon == null) { // Failed to load from resource, try loading from DB. @@ -72,7 +73,7 @@ public class CursorIconInfo { * Loads the fixed bitmap from the icon if available. */ public Bitmap loadIcon(Cursor c) { - return Utilities.createIconBitmap(c, iconIndex, mContext); + return LauncherIcons.createIconBitmap(c, iconIndex, mContext); } /** diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java index 163c953bb..afc45fe35 100644 --- a/src/com/android/launcher3/util/FocusLogic.java +++ b/src/com/android/launcher3/util/FocusLogic.java @@ -79,8 +79,7 @@ public class FocusLogic { return (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_MOVE_HOME || keyCode == KeyEvent.KEYCODE_MOVE_END || - keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN || - keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL); + keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN); } public static int handleKeyEvent(int keyCode, int [][] map, int iconIdx, int pageIndex, diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java index 46e9184b4..8f985c344 100644 --- a/src/com/android/launcher3/util/ItemInfoMatcher.java +++ b/src/com/android/launcher3/util/ItemInfoMatcher.java @@ -18,7 +18,9 @@ package com.android.launcher3.util; import android.content.ComponentName; +import com.android.launcher3.FolderInfo; import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.compat.UserHandleCompat; @@ -33,6 +35,46 @@ public abstract class ItemInfoMatcher { public abstract boolean matches(ItemInfo info, ComponentName cn); + /** + * Filters {@param infos} to those satisfying the {@link #matches(ItemInfo, ComponentName)}. + */ + public final HashSet<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos) { + HashSet<ItemInfo> filtered = new HashSet<>(); + for (ItemInfo i : infos) { + if (i instanceof ShortcutInfo) { + ShortcutInfo info = (ShortcutInfo) i; + ComponentName cn = info.getTargetComponent(); + if (cn != null && matches(info, cn)) { + filtered.add(info); + } + } else if (i instanceof FolderInfo) { + FolderInfo info = (FolderInfo) i; + for (ShortcutInfo s : info.contents) { + ComponentName cn = s.getTargetComponent(); + if (cn != null && matches(s, cn)) { + filtered.add(s); + } + } + } else if (i instanceof LauncherAppWidgetInfo) { + LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i; + ComponentName cn = info.providerName; + if (cn != null && matches(info, cn)) { + filtered.add(info); + } + } + } + return filtered; + } + + public static ItemInfoMatcher ofUser(final UserHandleCompat user) { + return new ItemInfoMatcher() { + @Override + public boolean matches(ItemInfo info, ComponentName cn) { + return info.user.equals(user); + } + }; + } + public static ItemInfoMatcher ofComponents( final HashSet<ComponentName> components, final UserHandleCompat user) { return new ItemInfoMatcher() { diff --git a/src/com/android/launcher3/util/NoLocaleSqliteContext.java b/src/com/android/launcher3/util/NoLocaleSqliteContext.java index 3b258e4a5..c8a5ffb1a 100644 --- a/src/com/android/launcher3/util/NoLocaleSqliteContext.java +++ b/src/com/android/launcher3/util/NoLocaleSqliteContext.java @@ -11,9 +11,6 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory; */ public class NoLocaleSqliteContext extends ContextWrapper { - // TODO: Use the flag defined in Context when the new SDK is available - private static final int MODE_NO_LOCALIZED_COLLATORS = 0x0010; - public NoLocaleSqliteContext(Context context) { super(context); } @@ -22,6 +19,6 @@ public class NoLocaleSqliteContext extends ContextWrapper { public SQLiteDatabase openOrCreateDatabase( String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) { return super.openOrCreateDatabase( - name, mode | MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler); + name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler); } } diff --git a/src/com/android/launcher3/util/StringFilter.java b/src/com/android/launcher3/util/StringFilter.java deleted file mode 100644 index f539ad11e..000000000 --- a/src/com/android/launcher3/util/StringFilter.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.android.launcher3.util; - -import java.util.Set; - -/** - * Abstract class to filter a set of strings. - */ -public abstract class StringFilter { - - private StringFilter() { } - - public abstract boolean matches(String str); - - public static StringFilter matchesAll() { - return new StringFilter() { - @Override - public boolean matches(String str) { - return true; - } - }; - } - - public static StringFilter of(final Set<String> validEntries) { - return new StringFilter() { - @Override - public boolean matches(String str) { - return validEntries.contains(str); - } - }; - } -} diff --git a/src/com/android/launcher3/util/TouchController.java b/src/com/android/launcher3/util/TouchController.java index d1409c8b9..3cca21500 100644 --- a/src/com/android/launcher3/util/TouchController.java +++ b/src/com/android/launcher3/util/TouchController.java @@ -1,8 +1,32 @@ +/* + * 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.view.MotionEvent; public interface TouchController { - boolean onTouchEvent(MotionEvent ev); - boolean onInterceptTouchEvent(MotionEvent ev); + + /** + * Called when the draglayer receives touch event. + */ + boolean onControllerTouchEvent(MotionEvent ev); + + /** + * Called when the draglayer receives a intercept touch event. + */ + boolean onControllerInterceptTouchEvent(MotionEvent ev); } diff --git a/src/com/android/launcher3/widget/PendingItemPreviewProvider.java b/src/com/android/launcher3/widget/PendingItemPreviewProvider.java index eaa0bb3d5..7c06701e8 100644 --- a/src/com/android/launcher3/widget/PendingItemPreviewProvider.java +++ b/src/com/android/launcher3/widget/PendingItemPreviewProvider.java @@ -21,7 +21,7 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.view.View; -import com.android.launcher3.HolographicOutlineHelper; +import com.android.launcher3.graphics.HolographicOutlineHelper; import com.android.launcher3.Launcher; import com.android.launcher3.PendingAddItemInfo; import com.android.launcher3.Workspace; @@ -67,7 +67,7 @@ public class PendingItemPreviewProvider extends DragPreviewProvider { // 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()) + HolographicOutlineHelper.getInstance(mView.getContext()) .applyExpensiveOutlineWithBlur(b, canvas, clipAlpha); canvas.setBitmap(null); diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 89c44c859..8b7225821 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -21,7 +21,6 @@ import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView.State; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -45,11 +44,11 @@ import com.android.launcher3.Utilities; import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.Workspace; import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.graphics.LauncherIcons; 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. @@ -64,12 +63,9 @@ public class WidgetsContainerView extends BaseContainerView private DragController mDragController; private IconCache mIconCache; - private final Rect mTmpBgPaddingRect = new Rect(); - /* Recycler view related member variables */ private WidgetsRecyclerView mRecyclerView; private WidgetsListAdapter mAdapter; - private TransformingTouchDelegate mRecyclerViewTouchDelegate; /* Touch handling related member variables. */ private Toast mWidgetInstructionToast; @@ -97,14 +93,8 @@ 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); + public View getTouchDelegateTargetView() { + return mRecyclerView; } @Override @@ -113,13 +103,6 @@ public class WidgetsContainerView extends BaseContainerView mRecyclerView = (WidgetsRecyclerView) getContentView().findViewById(R.id.widgets_list_view); mRecyclerView.setAdapter(mAdapter); mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - mRecyclerViewTouchDelegate = new TransformingTouchDelegate(mRecyclerView); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - ((View) mRecyclerView.getParent()).setTouchDelegate(mRecyclerViewTouchDelegate); } // @@ -241,7 +224,7 @@ public class WidgetsContainerView extends BaseContainerView } else { PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag(); Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.activityInfo); - preview = Utilities.createIconBitmap(icon, mLauncher); + preview = LauncherIcons.createIconBitmap(icon, mLauncher); createItemInfo.spanX = createItemInfo.spanY = 1; scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / preview.getWidth(); } diff --git a/tests/src/com/android/launcher3/BindWidgetTest.java b/tests/src/com/android/launcher3/BindWidgetTest.java index 5c5069fea..c133bf6c8 100644 --- a/tests/src/com/android/launcher3/BindWidgetTest.java +++ b/tests/src/com/android/launcher3/BindWidgetTest.java @@ -232,7 +232,6 @@ public class BindWidgetTest extends LauncherInstrumentationTestCase { runTestOnUiThread(new Runnable() { @Override public void run() { - LauncherClings.markFirstRunClingDismissed(mTargetContext); ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext); LauncherAppState.getInstance().getModel().resetLoadedState(true, true); } diff --git a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java index e858d17f3..e94fca6ff 100644 --- a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java +++ b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java @@ -23,7 +23,6 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetProviderInfo; -import com.android.launcher3.LauncherClings; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -189,7 +188,6 @@ public class LauncherInstrumentationTestCase extends InstrumentationTestCase { LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); LauncherSettings.Settings.call(mTargetContext.getContentResolver(), LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); - LauncherClings.markFirstRunClingDismissed(mTargetContext); ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext); runTestOnUiThread(new Runnable() { diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java index eee567fb8..79aed806c 100644 --- a/tests/src/com/android/launcher3/util/FocusLogicTest.java +++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java @@ -49,8 +49,6 @@ public final class FocusLogicTest extends AndroidTestCase { assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_END)); assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_UP)); assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_DOWN)); - assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DEL)); - assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_FORWARD_DEL)); } public void testCreateSparseMatrix() { |