diff options
35 files changed, 637 insertions, 562 deletions
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java index affad0f45..ee0d8b0c6 100644 --- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java +++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java @@ -131,7 +131,7 @@ public class WallpaperCropActivity extends BaseActivity implements Handler.Callb // Load image in background final BitmapRegionTileSource.UriBitmapSource bitmapSource = - new BitmapRegionTileSource.UriBitmapSource(getContext(), imageUri, 1024); + new BitmapRegionTileSource.UriBitmapSource(getContext(), imageUri); mSetWallpaperButton.setEnabled(false); Runnable onLoad = new Runnable() { public void run() { diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java index 1ba5b4b2e..72cb194d6 100644 --- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java +++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java @@ -21,7 +21,6 @@ import android.annotation.TargetApi; import android.app.ActionBar; import android.app.Activity; import android.app.WallpaperManager; -import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -144,8 +143,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { public void onClick(final WallpaperPickerActivity a) { a.setWallpaperButtonEnabled(false); final BitmapRegionTileSource.UriBitmapSource bitmapSource = - new BitmapRegionTileSource.UriBitmapSource( - a.getContext(), mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE); + new BitmapRegionTileSource.UriBitmapSource(a.getContext(), mUri); a.setCropViewTileSource(bitmapSource, true, false, null, new Runnable() { @Override @@ -199,8 +197,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { public void onClick(final WallpaperPickerActivity a) { a.setWallpaperButtonEnabled(false); BitmapRegionTileSource.UriBitmapSource bitmapSource = - new BitmapRegionTileSource.UriBitmapSource(a.getContext(), - Uri.fromFile(mFile), 1024); + new BitmapRegionTileSource.UriBitmapSource(a.getContext(), Uri.fromFile(mFile)); a.setCropViewTileSource(bitmapSource, false, true, null, new Runnable() { @Override @@ -236,8 +233,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { public void onClick(final WallpaperPickerActivity a) { a.setWallpaperButtonEnabled(false); BitmapRegionTileSource.ResourceBitmapSource bitmapSource = - new BitmapRegionTileSource.ResourceBitmapSource( - mResources, mResId, BitmapRegionTileSource.MAX_PREVIEW_SIZE); + new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId); a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleProvider() { @Override diff --git a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java index 15f97e5b1..2d496a5a6 100644 --- a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java +++ b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java @@ -26,6 +26,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.net.Uri; +import android.opengl.GLUtils; import android.os.Build; import android.util.Log; @@ -149,18 +150,15 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { private static final int GL_SIZE_LIMIT = 2048; // This must be no larger than half the size of the GL_SIZE_LIMIT // due to decodePreview being allowed to be up to 2x the size of the target - public static final int MAX_PREVIEW_SIZE = GL_SIZE_LIMIT / 2; + private static final int MAX_PREVIEW_SIZE = GL_SIZE_LIMIT / 2; public static abstract class BitmapSource { private SimpleBitmapRegionDecoder mDecoder; private Bitmap mPreview; - private final int mPreviewSize; private int mRotation; public enum State { NOT_LOADED, LOADED, ERROR_LOADING }; private State mState = State.NOT_LOADED; - public BitmapSource(int previewSize) { - mPreviewSize = Math.min(previewSize, MAX_PREVIEW_SIZE); - } + public boolean loadInBackground(InBitmapProvider bitmapProvider) { ExifInterface ei = new ExifInterface(); if (readExif(ei)) { @@ -176,36 +174,44 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { } else { int width = mDecoder.getWidth(); int height = mDecoder.getHeight(); - if (mPreviewSize != 0) { - BitmapFactory.Options opts = new BitmapFactory.Options(); - opts.inPreferredConfig = Bitmap.Config.ARGB_8888; - opts.inPreferQualityOverSpeed = true; - - float scale = (float) mPreviewSize / Math.max(width, height); - opts.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale); - opts.inJustDecodeBounds = false; - opts.inMutable = true; - - if (bitmapProvider != null) { - int expectedPixles = (width / opts.inSampleSize) * (height / opts.inSampleSize); - Bitmap reusableBitmap = bitmapProvider.forPixelCount(expectedPixles); - if (reusableBitmap != null) { - // Try loading with reusable bitmap - opts.inBitmap = reusableBitmap; - try { - mPreview = loadPreviewBitmap(opts); - } catch (IllegalArgumentException e) { - Log.d(TAG, "Unable to reusage bitmap", e); - opts.inBitmap = null; - mPreview = null; - } + + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inPreferredConfig = Bitmap.Config.ARGB_8888; + opts.inPreferQualityOverSpeed = true; + + float scale = (float) MAX_PREVIEW_SIZE / Math.max(width, height); + opts.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale); + opts.inJustDecodeBounds = false; + opts.inMutable = true; + + if (bitmapProvider != null) { + int expectedPixles = (width / opts.inSampleSize) * (height / opts.inSampleSize); + Bitmap reusableBitmap = bitmapProvider.forPixelCount(expectedPixles); + if (reusableBitmap != null) { + // Try loading with reusable bitmap + opts.inBitmap = reusableBitmap; + try { + mPreview = loadPreviewBitmap(opts); + } catch (IllegalArgumentException e) { + Log.d(TAG, "Unable to reusage bitmap", e); + opts.inBitmap = null; + mPreview = null; } } - if (mPreview == null) { - mPreview = loadPreviewBitmap(opts); - } } - mState = State.LOADED; + if (mPreview == null) { + mPreview = loadPreviewBitmap(opts); + } + + // Verify that the bitmap can be used on GL surface + try { + GLUtils.getInternalFormat(mPreview); + GLUtils.getType(mPreview); + mState = State.LOADED; + } catch (IllegalArgumentException e) { + Log.d(TAG, "Image cannot be rendered on a GL surface", e); + mState = State.ERROR_LOADING; + } return true; } } @@ -237,8 +243,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { public static class FilePathBitmapSource extends BitmapSource { private String mPath; - public FilePathBitmapSource(String path, int previewSize) { - super(previewSize); + public FilePathBitmapSource(String path) { mPath = path; } @Override @@ -272,8 +277,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { public static class UriBitmapSource extends BitmapSource { private Context mContext; private Uri mUri; - public UriBitmapSource(Context context, Uri uri, int previewSize) { - super(previewSize); + public UriBitmapSource(Context context, Uri uri) { mContext = context; mUri = uri; } @@ -337,8 +341,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { public static class ResourceBitmapSource extends BitmapSource { private Resources mRes; private int mResId; - public ResourceBitmapSource(Resources res, int resId, int previewSize) { - super(previewSize); + public ResourceBitmapSource(Resources res, int resId) { mRes = res; mResId = resId; } diff --git a/res/drawable-hdpi/bg_appwidget_error.9.png b/res/drawable-hdpi/bg_appwidget_error.9.png Binary files differdeleted file mode 100644 index 4da3195d4..000000000 --- a/res/drawable-hdpi/bg_appwidget_error.9.png +++ /dev/null diff --git a/res/drawable-mdpi/bg_appwidget_error.9.png b/res/drawable-mdpi/bg_appwidget_error.9.png Binary files differdeleted file mode 100644 index 493c0d454..000000000 --- a/res/drawable-mdpi/bg_appwidget_error.9.png +++ /dev/null diff --git a/res/drawable-xhdpi/bg_appwidget_error.9.png b/res/drawable-xhdpi/bg_appwidget_error.9.png Binary files differdeleted file mode 100644 index b792cc847..000000000 --- a/res/drawable-xhdpi/bg_appwidget_error.9.png +++ /dev/null diff --git a/res/layout/apps_grid_icon_view.xml b/res/layout/apps_grid_icon_view.xml index 67d7d50e7..7165f383d 100644 --- a/res/layout/apps_grid_icon_view.xml +++ b/res/layout/apps_grid_icon_view.xml @@ -25,6 +25,5 @@ android:paddingBottom="@dimen/apps_icon_top_bottom_padding" android:focusable="true" android:background="@drawable/focusable_view_bg" - launcher:deferShadowGeneration="true" launcher:iconDisplay="all_apps" /> diff --git a/res/layout/apps_list_view.xml b/res/layout/apps_list_view.xml index 03ba646ee..6f9be05af 100644 --- a/res/layout/apps_list_view.xml +++ b/res/layout/apps_list_view.xml @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" +<com.android.launcher3.AppsRecyclerViewContainer + xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/apps_list" android:layout_width="match_parent" android:layout_height="match_parent" @@ -101,4 +102,4 @@ android:src="@drawable/ic_search_grey" /> </FrameLayout> -</FrameLayout>
\ No newline at end of file +</com.android.launcher3.AppsRecyclerViewContainer>
\ No newline at end of file diff --git a/res/layout/apps_prediction_bar_icon_view.xml b/res/layout/apps_prediction_bar_icon_view.xml index 4a6f1574b..1e75d14b6 100644 --- a/res/layout/apps_prediction_bar_icon_view.xml +++ b/res/layout/apps_prediction_bar_icon_view.xml @@ -24,6 +24,5 @@ android:layout_weight="1" android:focusable="true" android:background="@drawable/focusable_view_bg" - launcher:deferShadowGeneration="true" launcher:iconDisplay="all_apps" /> diff --git a/res/layout/appwidget_error.xml b/res/layout/appwidget_error.xml index 03d4ae424..708ece4e2 100644 --- a/res/layout/appwidget_error.xml +++ b/res/layout/appwidget_error.xml @@ -15,15 +15,12 @@ --> <TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingTop="10dip" - android:paddingBottom="10dip" - android:paddingLeft="20dip" - android:paddingRight="20dip" + android:layout_width="match_parent" + android:layout_height="match_parent" android:gravity="center" - android:background="@drawable/bg_appwidget_error" + android:elevation="2dp" + android:background="@drawable/quantum_panel_dark" android:textAppearance="?android:attr/textAppearanceMediumInverse" - android:textColor="@color/appwidget_error_color" + android:textColor="@color/widgets_view_item_text_color" android:text="@string/gadget_error_text" /> diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml index 500cf107f..7fefebaec 100644 --- a/res/layout/widget_cell.xml +++ b/res/layout/widget_cell.xml @@ -39,16 +39,15 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" - android:gravity="start" - android:singleLine="true" android:ellipsize="end" android:fadingEdge="horizontal" - android:textColor="@color/widgets_view_item_text_color" - android:textSize="14sp" - android:textAlignment="viewStart" android:fontFamily="sans-serif-condensed" + android:gravity="start" + android:shadowColor="#B0000000" android:shadowRadius="2.0" - android:shadowColor="#B0000000" /> + android:singleLine="true" + android:textColor="@color/widgets_view_item_text_color" + android:textSize="14sp" /> <!-- The original dimensions of the widget (can't be the same text as above due to different style. --> @@ -58,22 +57,18 @@ android:layout_height="wrap_content" android:layout_marginStart="5dp" android:layout_marginLeft="5dp" - android:layout_weight="0" - android:gravity="start" android:textColor="@color/widgets_view_item_text_color" android:textSize="14sp" - android:textAlignment="viewStart" android:fontFamily="sans-serif-condensed" android:shadowRadius="2.0" android:shadowColor="#B0000000" /> </LinearLayout> - <!-- The image of the widget. --> + <!-- The image of the widget. This view does not support padding. Any placement adjustment + should be done using margins. --> <com.android.launcher3.widget.WidgetImageView android:id="@+id/widget_preview" - style="@style/WidgetImageView" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_weight="1" - android:scaleType="matrix" /> + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> </com.android.launcher3.widget.WidgetCell>
\ No newline at end of file diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml index b91ca2618..1857595e8 100644 --- a/res/layout/widgets_view.xml +++ b/res/layout/widgets_view.xml @@ -40,6 +40,7 @@ android:layout_height="match_parent" android:clipChildren="false" android:elevation="15dp" + android:visibility="gone" android:orientation="vertical" > <com.android.launcher3.widget.WidgetsContainerRecyclerView diff --git a/res/values-v17/styles.xml b/res/values-v17/styles.xml deleted file mode 100644 index 3589e80ac..000000000 --- a/res/values-v17/styles.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <style name="WidgetImageView"> - <item name="android:paddingStart">@dimen/widget_preview_horizontal_padding</item> - </style> -</resources> diff --git a/res/values/colors.xml b/res/values/colors.xml index 0ba55f326..1e89615af 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -27,8 +27,6 @@ <color name="focused_background">#80c6c5c5</color> - <color name="appwidget_error_color">#FCCC</color> - <color name="workspace_icon_text_color">#FFF</color> <color name="quantum_panel_text_color">#FF666666</color> diff --git a/res/values/styles.xml b/res/values/styles.xml index f95debeab..1496da994 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -86,9 +86,4 @@ <item name="ringOutset">4dp</item> </style> - <!-- Overridden in device overlays --> - <style name="WidgetImageView"> - <item name="android:paddingLeft">@dimen/widget_preview_horizontal_padding</item> - </style> - </resources> diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java index eff7b0625..82aaeb99a 100644 --- a/src/com/android/launcher3/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/AlphabeticalAppsList.java @@ -7,6 +7,7 @@ import android.util.Log; import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.model.AppNameComparator; import java.text.Collator; import java.util.ArrayList; @@ -18,96 +19,6 @@ import java.util.Locale; import java.util.Map; import java.util.TreeMap; - -/** - * A private class to manage access to an app name comparator. - */ -class AppNameComparator { - private final UserManagerCompat mUserManager; - private final Collator mCollator; - private final Comparator<AppInfo> mAppInfoComparator; - private final Comparator<String> mSectionNameComparator; - private HashMap<UserHandleCompat, Long> mUserSerialCache = new HashMap<>(); - - public AppNameComparator(Context context) { - mCollator = Collator.getInstance(); - mUserManager = UserManagerCompat.getInstance(context); - mAppInfoComparator = new Comparator<AppInfo>() { - public final int compare(AppInfo a, AppInfo b) { - // Order by the title in the current locale - int result = compareTitles(a.title.toString(), b.title.toString()); - if (result == 0) { - // If two apps have the same title, then order by the component name - result = a.componentName.compareTo(b.componentName); - if (result == 0) { - // If the two apps are the same component, then prioritize by the order that - // the app user was created (prioritizing the main user's apps) - if (UserHandleCompat.myUserHandle().equals(a.user)) { - return -1; - } else { - Long aUserSerial = getAndCacheUserSerial(a.user); - Long bUserSerial = getAndCacheUserSerial(b.user); - return aUserSerial.compareTo(bUserSerial); - } - } - } - return result; - } - }; - mSectionNameComparator = new Comparator<String>() { - @Override - public int compare(String o1, String o2) { - return compareTitles(o1, o2); - } - }; - } - - /** - * Returns a locale-aware comparator that will alphabetically order a list of applications. - */ - public Comparator<AppInfo> getAppInfoComparator() { - // Clear the user serial cache so that we get serials as needed in the comparator - mUserSerialCache.clear(); - return mAppInfoComparator; - } - - /** - * Returns a locale-aware comparator that will alphabetically order a list of section names. - */ - public Comparator<String> getSectionNameComparator() { - return mSectionNameComparator; - } - - /** - * Compares two titles with the same return value semantics as Comparator. - */ - private int compareTitles(String titleA, String titleB) { - // Ensure that we de-prioritize any titles that don't start with a linguistic letter or digit - boolean aStartsWithLetter = Character.isLetterOrDigit(titleA.codePointAt(0)); - boolean bStartsWithLetter = Character.isLetterOrDigit(titleB.codePointAt(0)); - if (aStartsWithLetter && !bStartsWithLetter) { - return -1; - } else if (!aStartsWithLetter && bStartsWithLetter) { - return 1; - } - - // Order by the title in the current locale - return mCollator.compare(titleA, titleB); - } - - /** - * Returns the user serial for this user, using a cached serial if possible. - */ - private Long getAndCacheUserSerial(UserHandleCompat user) { - Long userSerial = mUserSerialCache.get(user); - if (userSerial == null) { - userSerial = mUserManager.getSerialNumberForUser(user); - mUserSerialCache.put(user, userSerial); - } - return userSerial; - } -} - /** * The alphabetically sorted list of applications. */ diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java index 5b1da4b0c..31942b36d 100644 --- a/src/com/android/launcher3/AppsContainerRecyclerView.java +++ b/src/com/android/launcher3/AppsContainerRecyclerView.java @@ -122,30 +122,20 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { mApps = apps; } - @Override - public void setAdapter(Adapter adapter) { - // Register a change listener to update the scroll position state whenever the data set - // changes. - adapter.registerAdapterDataObserver(new AdapterDataObserver() { - @Override - public void onChanged() { - post(new Runnable() { - @Override - public void run() { - refreshCurScrollPosition(); - } - }); - } - }); - super.setAdapter(adapter); - } - /** * Sets the number of apps per row in this recycler view. */ public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) { mNumAppsPerRow = numAppsPerRow; mNumPredictedAppsPerRow = numPredictedAppsPerRow; + + DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile(); + RecyclerView.RecycledViewPool pool = getRecycledViewPool(); + int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx); + pool.setMaxRecycledViews(AppsGridAdapter.PREDICTION_BAR_SPACER_TYPE, 1); + pool.setMaxRecycledViews(AppsGridAdapter.EMPTY_VIEW_TYPE, 1); + pool.setMaxRecycledViews(AppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow); + pool.setMaxRecycledViews(AppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows); } public void updateBackgroundPadding(Drawable background) { @@ -186,7 +176,20 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { */ public void scrollToTop() { scrollToPosition(0); - updateScrollY(0); + } + + /** + * Returns the current scroll position. + */ + public int getScrollPosition() { + List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems(); + getCurScrollState(mScrollPosState, items); + if (mScrollPosState.rowIndex != -1) { + int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; + return getPaddingTop() + (mScrollPosState.rowIndex * mScrollPosState.rowHeight) + + predictionBarHeight - mScrollPosState.rowTopOffset; + } + return 0; } @Override @@ -357,7 +360,6 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { if (touchFraction <= predictionBarFraction) { // Scroll to the top of the view, where the prediction bar is layoutManager.scrollToPositionWithOffset(0, 0); - updateScrollY(0); return ""; } } @@ -376,9 +378,6 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { lastScrollSection = scrollSection; } - // We need to workaround the RecyclerView to get the right scroll position - refreshCurScrollPosition(); - // Scroll to the view at the position, anchored at the top of the screen. We call the scroll // method on the LayoutManager directly since it is not exposed by RecyclerView. layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0); @@ -402,7 +401,6 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { int x; int y; int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; - boolean isRtl = Utilities.isRtl(getResources()); int rowCount = getNumRows(); getCurScrollState(mScrollPosState, items); if (mScrollPosState.rowIndex != -1) { @@ -413,7 +411,7 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { (int) (height / ((float) totalScrollHeight / height))); // Calculate the position and size of the scroll bar - if (isRtl) { + if (Utilities.isRtl(getResources())) { x = mBackgroundPadding.left; } else { x = getWidth() - mBackgroundPadding.right - mScrollbarWidth; @@ -442,11 +440,10 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { if (mFastScrollAlpha > 0f && !mFastScrollSectionName.isEmpty()) { int x; int y; - boolean isRtl = Utilities.isRtl(getResources()); // Calculate the position for the fast scroller popup Rect bgBounds = mFastScrollerBg.getBounds(); - if (isRtl) { + if (Utilities.isRtl(getResources())) { x = mBackgroundPadding.left + getScrollBarSize(); } else { x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width(); @@ -492,20 +489,6 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { } /** - * Forces a refresh of the scroll position to any scroll listener. - */ - private void refreshCurScrollPosition() { - List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems(); - getCurScrollState(mScrollPosState, items); - if (mScrollPosState.rowIndex != -1) { - int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; - int scrollY = getPaddingTop() + (mScrollPosState.rowIndex * mScrollPosState.rowHeight) + - predictionBarHeight - mScrollPosState.rowTopOffset; - updateScrollY(scrollY); - } - } - - /** * Returns the current scroll state. */ private void getCurScrollState(ScrollPositionState stateOut, diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java index 8709101f4..5ccb6c620 100644 --- a/src/com/android/launcher3/AppsContainerView.java +++ b/src/com/android/launcher3/AppsContainerView.java @@ -35,6 +35,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; @@ -47,12 +48,97 @@ import java.util.regex.Pattern; /** + * Interface for controlling the header elevation in response to RecyclerView scroll. + */ +interface HeaderElevationController { + void onScroll(int scrollY); + void disable(); +} + +/** + * Implementation of the header elevation mechanism for pre-L devices. It simulates elevation + * by drawing a gradient under the header bar. + */ +final class HeaderElevationControllerV16 implements HeaderElevationController { + + private final View mShadow; + + private final float mScrollToElevation; + + public HeaderElevationControllerV16(View header) { + Resources res = header.getContext().getResources(); + mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation); + + mShadow = new View(header.getContext()); + mShadow.setBackground(new GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, new int[] {0x44000000, 0x00000000})); + mShadow.setAlpha(0); + + FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + res.getDimensionPixelSize(R.dimen.all_apps_header_shadow_height)); + lp.topMargin = ((FrameLayout.LayoutParams) header.getLayoutParams()).height; + + ((ViewGroup) header.getParent()).addView(mShadow, lp); + } + + @Override + public void onScroll(int scrollY) { + float elevationPct = (float) Math.min(scrollY, mScrollToElevation) / + mScrollToElevation; + mShadow.setAlpha(elevationPct); + } + + @Override + public void disable() { + ViewGroup parent = (ViewGroup) mShadow.getParent(); + if (parent != null) { + parent.removeView(mShadow); + } + } +} + +/** + * Implementation of the header elevation mechanism for L+ devices, which makes use of the native + * view elevation. + */ +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +final class HeaderElevationControllerVL implements HeaderElevationController { + + private final View mHeader; + private final float mMaxElevation; + private final float mScrollToElevation; + + public HeaderElevationControllerVL(View header) { + mHeader = header; + + Resources res = header.getContext().getResources(); + mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation); + mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation); + } + + @Override + public void onScroll(int scrollY) { + float elevationPct = (float) Math.min(scrollY, mScrollToElevation) / + mScrollToElevation; + float newElevation = mMaxElevation * elevationPct; + if (Float.compare(mHeader.getElevation(), newElevation) != 0) { + mHeader.setElevation(newElevation); + } + } + + @Override + public void disable() { } +} + +/** * The all apps view container. */ public class AppsContainerView extends BaseContainerView implements DragSource, Insettable, TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable, AlphabeticalAppsList.FilterChangedCallback, AppsGridAdapter.PredictionBarSpacerCallbacks, - View.OnTouchListener, View.OnClickListener, View.OnLongClickListener { + View.OnTouchListener, View.OnClickListener, View.OnLongClickListener, + ViewTreeObserver.OnPreDrawListener { public static final boolean GRID_MERGE_SECTIONS = true; @@ -96,8 +182,7 @@ public class AppsContainerView extends BaseContainerView implements DragSource, // Normal container insets private int mContainerInset; private int mPredictionBarHeight; - // RecyclerView scroll position - @Thunk int mRecyclerViewScrollY; + private int mLastRecyclerViewScrollPos = -1; private CheckLongPressHelper mPredictionIconCheckForLongPress; private View mPredictionIconUnderTouch; @@ -266,14 +351,6 @@ public class AppsContainerView extends BaseContainerView implements DragSource, mAppsRecyclerView.setLayoutManager(mLayoutManager); mAppsRecyclerView.setAdapter(mAdapter); mAppsRecyclerView.setHasFixedSize(true); - mAppsRecyclerView.setOnScrollListenerProxy( - new BaseContainerRecyclerView.OnScrollToListener() { - @Override - public void onScrolledTo(int x, int y) { - mRecyclerViewScrollY = y; - onRecyclerViewScrolled(); - } - }); if (mItemDecoration != null) { mAppsRecyclerView.addItemDecoration(mItemDecoration); } @@ -283,9 +360,7 @@ public class AppsContainerView extends BaseContainerView implements DragSource, @Override public void onBindPredictionBar() { - if (!updatePredictionBarVisibility()) { - return; - } + updatePredictionBarVisibility(); List<AppInfo> predictedApps = mApps.getPredictedApps(); int childCount = mPredictionBarView.getChildCount(); @@ -401,6 +476,12 @@ public class AppsContainerView extends BaseContainerView implements DragSource, } @Override + public boolean onPreDraw() { + synchronizeToRecyclerViewScrollPosition(mAppsRecyclerView.getScrollPosition()); + return true; + } + + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return handleTouchEvent(ev); } @@ -600,7 +681,11 @@ public class AppsContainerView extends BaseContainerView implements DragSource, @Override public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { - // Do nothing + // Register for a pre-draw listener to synchronize the recycler view scroll to other views + // in this container + if (!toWorkspace) { + getViewTreeObserver().addOnPreDrawListener(this); + } } @Override @@ -620,18 +705,26 @@ public class AppsContainerView extends BaseContainerView implements DragSource, hideSearchField(false, false); } } + if (toWorkspace) { + getViewTreeObserver().removeOnPreDrawListener(this); + mLastRecyclerViewScrollPos = -1; + } } /** * Updates the container when the recycler view is scrolled. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private void onRecyclerViewScrolled() { - if (DYNAMIC_HEADER_ELEVATION) { - mElevationController.onScroll(mRecyclerViewScrollY); - } + private void synchronizeToRecyclerViewScrollPosition(int scrollY) { + if (mLastRecyclerViewScrollPos != scrollY) { + mLastRecyclerViewScrollPos = scrollY; + if (DYNAMIC_HEADER_ELEVATION) { + mElevationController.onScroll(scrollY); + } - mPredictionBarView.setTranslationY(-mRecyclerViewScrollY + mAppsRecyclerView.getPaddingTop()); + // Scroll the prediction bar with the contents of the recycler view + mPredictionBarView.setTranslationY(-scrollY + mAppsRecyclerView.getPaddingTop()); + } } @Override @@ -686,6 +779,19 @@ public class AppsContainerView extends BaseContainerView implements DragSource, } } break; + case MotionEvent.ACTION_MOVE: + if (mPredictionIconUnderTouch != null) { + float dist = (float) Math.hypot(x - mPredictionIconTouchDownPos.x, + y - mPredictionIconTouchDownPos.y); + if (dist > ViewConfiguration.get(getContext()).getScaledTouchSlop()) { + if (mPredictionIconCheckForLongPress != null) { + mPredictionIconCheckForLongPress.cancelLongPress(); + } + mPredictionIconCheckForLongPress = null; + mPredictionIconUnderTouch = null; + } + } + break; case MotionEvent.ACTION_UP: if (mBoundsCheckLastTouchDownPos.x > -1) { ViewConfiguration viewConfig = ViewConfiguration.get(getContext()); @@ -728,8 +834,19 @@ public class AppsContainerView extends BaseContainerView implements DragSource, * Returns the predicted app in the prediction bar given a set of local coordinates. */ private View findPredictedAppAtCoordinate(int x, int y) { - int[] coord = {x, y}; Rect hitRect = new Rect(); + + // Ensure we aren't hitting the search bar + int[] coord = {x, y}; + Utilities.mapCoordInSelfToDescendent(mHeaderView, this, coord); + mHeaderView.getHitRect(hitRect); + if (hitRect.contains(coord[0], coord[1])) { + return null; + } + + // Check against the children of the prediction bar + coord[0] = x; + coord[1] = y; Utilities.mapCoordInSelfToDescendent(mPredictionBarView, this, coord); for (int i = 0; i < mPredictionBarView.getChildCount(); i++) { View child = mPredictionBarView.getChildAt(i); @@ -843,79 +960,4 @@ public class AppsContainerView extends BaseContainerView implements DragSource, private InputMethodManager getInputMethodManager() { return (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); } - - private static interface HeaderElevationController { - - public void onScroll(int scrollY); - - public void disable(); - } - - private static final class HeaderElevationControllerV16 implements HeaderElevationController { - - private final View mShadow; - - private final float mScrollToElevation; - - public HeaderElevationControllerV16(View header) { - Resources res = header.getContext().getResources(); - mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation); - - mShadow = new View(header.getContext()); - mShadow.setBackground(new GradientDrawable( - GradientDrawable.Orientation.TOP_BOTTOM, new int[] {0x44000000, 0x00000000})); - mShadow.setAlpha(0); - - FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( - LayoutParams.MATCH_PARENT, - res.getDimensionPixelSize(R.dimen.all_apps_header_shadow_height)); - lp.topMargin = ((FrameLayout.LayoutParams) header.getLayoutParams()).height; - - ((ViewGroup) header.getParent()).addView(mShadow, lp); - } - - @Override - public void onScroll(int scrollY) { - float elevationPct = (float) Math.min(scrollY, mScrollToElevation) / - mScrollToElevation; - mShadow.setAlpha(elevationPct); - } - - @Override - public void disable() { - ViewGroup parent = (ViewGroup) mShadow.getParent(); - if (parent != null) { - parent.removeView(mShadow); - } - } - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static final class HeaderElevationControllerVL implements HeaderElevationController { - - private final View mHeader; - private final float mMaxElevation; - private final float mScrollToElevation; - - public HeaderElevationControllerVL(View header) { - mHeader = header; - - Resources res = header.getContext().getResources(); - mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation); - mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation); - } - - @Override - public void onScroll(int scrollY) { - float elevationPct = (float) Math.min(scrollY, mScrollToElevation) / - mScrollToElevation; - float newElevation = mMaxElevation * elevationPct; - if (Float.compare(mHeader.getElevation(), newElevation) != 0) { - mHeader.setElevation(newElevation); - } - } - - @Override - public void disable() { } - } } diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java index d5a411e66..580930cf2 100644 --- a/src/com/android/launcher3/AppsGridAdapter.java +++ b/src/com/android/launcher3/AppsGridAdapter.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.android.launcher3; import android.content.Context; diff --git a/src/com/android/launcher3/AppsRecyclerViewContainer.java b/src/com/android/launcher3/AppsRecyclerViewContainer.java new file mode 100644 index 000000000..cf4beca28 --- /dev/null +++ b/src/com/android/launcher3/AppsRecyclerViewContainer.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler; + +public class AppsRecyclerViewContainer extends FrameLayout implements BubbleTextShadowHandler { + + private final ClickShadowView mTouchFeedbackView; + + public AppsRecyclerViewContainer(Context context) { + this(context, null); + } + + public AppsRecyclerViewContainer(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public AppsRecyclerViewContainer(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + LauncherAppState app = LauncherAppState.getInstance(); + DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); + + mTouchFeedbackView = new ClickShadowView(context); + + // Make the feedback view large enough to hold the blur bitmap. + int size = grid.allAppsIconSizePx + mTouchFeedbackView.getExtraSize(); + addView(mTouchFeedbackView, size, size); + } + + @Override + public void setPressedIcon(BubbleTextView icon, Bitmap background) { + if (icon == null || background == null) { + mTouchFeedbackView.setBitmap(null); + mTouchFeedbackView.animate().cancel(); + } else if (mTouchFeedbackView.setBitmap(background)) { + mTouchFeedbackView.alignWithIconView(icon, (ViewGroup) icon.getParent()); + mTouchFeedbackView.animateShadow(); + } + } +} diff --git a/src/com/android/launcher3/BaseContainerRecyclerView.java b/src/com/android/launcher3/BaseContainerRecyclerView.java index 59e20ca2f..e52d88708 100644 --- a/src/com/android/launcher3/BaseContainerRecyclerView.java +++ b/src/com/android/launcher3/BaseContainerRecyclerView.java @@ -29,20 +29,11 @@ import com.android.launcher3.util.Thunk; public class BaseContainerRecyclerView extends RecyclerView implements RecyclerView.OnItemTouchListener { - /** - * Listener to get notified when the absolute scroll changes. - */ - public interface OnScrollToListener { - void onScrolledTo(int x, int y); - } - private static final int SCROLL_DELTA_THRESHOLD_DP = 4; /** Keeps the last known scrolling delta/velocity along y-axis. */ @Thunk int mDy = 0; - @Thunk int mScrollY; private float mDeltaThreshold; - private OnScrollToListener mScrollToListener; public BaseContainerRecyclerView(Context context) { this(context, null); @@ -68,20 +59,9 @@ public class BaseContainerRecyclerView extends RecyclerView @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { mDy = dy; - mScrollY += dy; - if (mScrollToListener != null) { - mScrollToListener.onScrolledTo(0, mScrollY); - } } } - /** - * Sets an additional scroll listener, only needed for LMR1 version of the support lib. - */ - public void setOnScrollListenerProxy(OnScrollToListener listener) { - mScrollToListener = listener; - } - @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -106,17 +86,6 @@ public class BaseContainerRecyclerView extends RecyclerView } /** - * Updates the scroll position, used to workaround a RecyclerView issue with scrolling to - * position. - */ - protected void updateScrollY(int scrollY) { - mScrollY = scrollY; - if (mScrollToListener != null) { - mScrollToListener.onScrolledTo(0, mScrollY); - } - } - - /** * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped. */ protected boolean shouldStopScroll(MotionEvent ev) { diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 5dc3b1239..3b3b9bf9f 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -28,11 +28,13 @@ import android.graphics.Region; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; +import android.util.Log; import android.util.SparseArray; import android.util.TypedValue; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ViewConfiguration; +import android.view.ViewParent; import android.widget.TextView; import com.android.launcher3.IconCache.IconLoadRequest; @@ -76,6 +78,7 @@ public class BubbleTextView extends TextView { private boolean mStayPressed; private boolean mIgnorePressedStateChange; + private boolean mDisableRelayout = false; private IconLoadRequest mIconLoadRequest; @@ -274,9 +277,10 @@ public class BubbleTextView extends TextView { } // Only show the shadow effect when persistent pressed state is set. - if (getParent() instanceof ShortcutAndWidgetContainer) { - CellLayout layout = (CellLayout) getParent().getParent(); - layout.setPressedIcon(this, mPressedBackground); + ViewParent parent = getParent(); + if (parent != null && parent.getParent() instanceof BubbleTextShadowHandler) { + ((BubbleTextShadowHandler) parent.getParent()).setPressedIcon( + this, mPressedBackground); } updateIconState(); @@ -464,12 +468,20 @@ public class BubbleTextView extends TextView { return icon; } + @Override + public void requestLayout() { + if (!mDisableRelayout) { + super.requestLayout(); + } + } + /** * Applies the item info if it is same as what the view is pointing to currently. */ public void reapplyItemInfo(final ItemInfo info) { if (getTag() == info) { mIconLoadRequest = null; + mDisableRelayout = true; if (info instanceof AppInfo) { applyFromApplicationInfo((AppInfo) info); } else if (info instanceof ShortcutInfo) { @@ -478,6 +490,7 @@ public class BubbleTextView extends TextView { } else if (info instanceof PackageItemInfo) { applyFromPackageItemInfo((PackageItemInfo) info); } + mDisableRelayout = false; } } @@ -509,4 +522,11 @@ public class BubbleTextView extends TextView { } } } + + /** + * Interface to be implemented by the grand parent to allow click shadow effect. + */ + public static interface BubbleTextShadowHandler { + void setPressedIcon(BubbleTextView icon, Bitmap background); + } } diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index a348008d8..57db805d8 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -47,6 +47,7 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.animation.DecelerateInterpolator; +import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler; import com.android.launcher3.FolderIcon.FolderRingAnimator; import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate; import com.android.launcher3.accessibility.FolderAccessibilityHelper; @@ -61,7 +62,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Stack; -public class CellLayout extends ViewGroup { +public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2; public static final int FOLDER_ACCESSIBILITY_DRAG = 1; @@ -409,7 +410,8 @@ public class CellLayout extends ViewGroup { invalidate(); } - void setPressedIcon(BubbleTextView icon, Bitmap background) { + @Override + public void setPressedIcon(BubbleTextView icon, Bitmap background) { if (icon == null || background == null) { mTouchFeedbackView.setBitmap(null); mTouchFeedbackView.animate().cancel(); diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java index f5c29ae0a..2191455d5 100644 --- a/src/com/android/launcher3/DragController.java +++ b/src/com/android/launcher3/DragController.java @@ -174,19 +174,18 @@ public class DragController { * @param dragInfo The data associated with the object that is being dragged * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or * {@link #DRAG_ACTION_COPY} + * @param viewImageBounds the position of the image inside the view * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. * Makes dragging feel more precise, e.g. you can clip out a transparent border */ - public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, int dragAction, - Point extraPadding, float initialDragViewScale) { + public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, + Rect viewImageBounds, int dragAction, float initialDragViewScale) { int[] loc = mCoordinatesTemp; mLauncher.getDragLayer().getLocationInDragLayer(v, loc); - int viewExtraPaddingLeft = extraPadding != null ? extraPadding.x : 0; - int viewExtraPaddingTop = extraPadding != null ? extraPadding.y : 0; - int dragLayerX = loc[0] + v.getPaddingLeft() + viewExtraPaddingLeft + - (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2); - int dragLayerY = loc[1] + v.getPaddingTop() + viewExtraPaddingTop + - (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2); + int dragLayerX = loc[0] + viewImageBounds.left + + (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2); + int dragLayerY = loc[1] + viewImageBounds.top + + (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2); startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, null, initialDragViewScale, false); diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index a828b1a42..51ba2dfc9 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -134,7 +134,7 @@ public class Launcher extends Activity View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener, LauncherStateTransitionAnimation.Callbacks { static final String TAG = "Launcher"; - static final boolean LOGD = true; + static final boolean LOGD = false; // Temporary flag static final boolean DISABLE_ALL_APPS_SEARCH_INTEGRATION = true; @@ -185,10 +185,6 @@ public class Launcher extends Activity private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x"; // Type: int private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y"; - // Type: boolean - private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder"; - // Type: long - private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id"; // Type: int private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x"; // Type: int @@ -261,8 +257,6 @@ public class Launcher extends Activity private int[] mTmpAddItemCellCoordinates = new int[2]; - private FolderInfo mFolderInfo; - private Hotseat mHotseat; private ViewGroup mOverviewPanel; @@ -480,11 +474,11 @@ public class Launcher extends Activity if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) { // If the user leaves launcher, then we should just load items asynchronously when // they return. - mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE); + mModel.startLoader(PagedView.INVALID_RESTORE_PAGE); } else { // We only load the page synchronously if the user rotates (or triggers a // configuration change) while launcher is in the foreground - mModel.startLoader(true, mWorkspace.getRestorePage()); + mModel.startLoader(mWorkspace.getRestorePage()); } } @@ -1051,7 +1045,7 @@ public class Launcher extends Activity mPaused = false; if (mRestoring || mOnResumeNeedsLoad) { setWorkspaceLoading(true); - mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE); + mModel.startLoader(PagedView.INVALID_RESTORE_PAGE); mRestoring = false; mOnResumeNeedsLoad = false; } @@ -1385,13 +1379,6 @@ public class Launcher extends Activity mRestoring = true; } - boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false); - if (renameFolder) { - long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID); - mFolderInfo = mModel.getFolderById(this, sFolders, id); - mRestoring = true; - } - mItemIdToViewId = (HashMap<Integer, Integer>) savedState.getSerializable(RUNTIME_STATE_VIEW_IDS); } @@ -1703,11 +1690,11 @@ public class Launcher extends Activity updateAutoAdvanceState(); } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) { mModel.resetLoadedState(false, true); - mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE, + mModel.startLoader(PagedView.INVALID_RESTORE_PAGE, LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE); } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) { mModel.resetLoadedState(false, true); - mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE, + mModel.startLoader(PagedView.INVALID_RESTORE_PAGE, LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS); } @@ -2035,11 +2022,6 @@ public class Launcher extends Activity outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId); } - if (mFolderInfo != null && mWaitingForResult) { - outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true); - outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id); - } - // Save the current widgets tray? // TODO(hyunyoungs) outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId); @@ -3431,7 +3413,7 @@ public class Launcher extends Activity * Shows the widgets view. */ void showWidgetsView(boolean animated, boolean resetPageToZero) { - Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero); + if (LOGD) Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero); if (resetPageToZero) { mWidgetsView.scrollToTop(); } @@ -3497,8 +3479,7 @@ public class Launcher extends Activity } public void enterSpringLoadedDragMode() { - Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", - mState.name())); + if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name())); if (mState == State.WORKSPACE || mState == State.APPS_SPRING_LOADED || mState == State.WIDGETS_SPRING_LOADED) { return; @@ -3693,7 +3674,7 @@ public class Launcher extends Activity */ private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) { if (mPaused) { - Log.i(TAG, "Deferring update until onResume"); + if (LOGD) Log.d(TAG, "Deferring update until onResume"); if (deletePreviousRunnables) { while (mBindOnResumeCallbacks.remove(run)) { } @@ -3729,7 +3710,7 @@ public class Launcher extends Activity */ public boolean setLoadOnResume() { if (mPaused) { - Log.i(TAG, "setLoadOnResume"); + if (LOGD) Log.d(TAG, "setLoadOnResume"); mOnResumeNeedsLoad = true; return true; } else { diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index d51df32b0..bde54c335 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -33,11 +33,9 @@ import android.view.WindowManager; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat; -import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.util.Thunk; import java.lang.ref.WeakReference; -import java.util.ArrayList; public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks { diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java index 63d3ebc23..c13752cb1 100644 --- a/src/com/android/launcher3/LauncherClings.java +++ b/src/com/android/launcher3/LauncherClings.java @@ -71,7 +71,7 @@ class LauncherClings implements OnClickListener { // Copy the shortcuts from the old database LauncherModel model = mLauncher.getModel(); model.resetLoadedState(false, true); - model.startLoader(false, PagedView.INVALID_RESTORE_PAGE, + model.startLoader(PagedView.INVALID_RESTORE_PAGE, LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS); // Set the flag to skip the folder cling diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index c56022ec6..58e899aec 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -66,7 +66,6 @@ import com.android.launcher3.util.Thunk; import java.lang.ref.WeakReference; import java.net.URISyntaxException; import java.security.InvalidParameterException; -import java.text.Collator; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -1346,40 +1345,32 @@ public class LauncherModel extends BroadcastReceiver } } if (runLoader) { - startLoader(false, PagedView.INVALID_RESTORE_PAGE); + startLoader(PagedView.INVALID_RESTORE_PAGE); } } - // If there is already a loader task running, tell it to stop. - // returns true if isLaunching() was true on the old task - private boolean stopLoaderLocked() { - boolean isLaunching = false; + /** + * If there is already a loader task running, tell it to stop. + */ + private void stopLoaderLocked() { LoaderTask oldTask = mLoaderTask; if (oldTask != null) { - if (oldTask.isLaunching()) { - isLaunching = true; - } oldTask.stopLocked(); } - return isLaunching; } public boolean isCurrentCallbacks(Callbacks callbacks) { return (mCallbacks != null && mCallbacks.get() == callbacks); } - public void startLoader(boolean isLaunching, int synchronousBindPage) { - startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE); + public void startLoader(int synchronousBindPage) { + startLoader(synchronousBindPage, LOADER_FLAG_NONE); } - public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) { + public void startLoader(int synchronousBindPage, int loadFlags) { // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems InstallShortcutReceiver.enableInstallQueue(); synchronized (mLock) { - if (DEBUG_LOADERS) { - Log.d(TAG, "startLoader isLaunching=" + isLaunching); - } - // Clear any deferred bind-runnables from the synchronized load process // We must do this before any loading/binding is scheduled below. synchronized (mDeferredBindRunnables) { @@ -1389,11 +1380,10 @@ public class LauncherModel extends BroadcastReceiver // Don't bother to start the thread if we know it's not going to do anything if (mCallbacks != null && mCallbacks.get() != null) { // If there is already one running, tell it to stop. - // also, don't downgrade isLaunching if we're already running - isLaunching = isLaunching || stopLoaderLocked(); - mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching, loadFlags); + stopLoaderLocked(); + mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags); if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE - && mAllAppsLoaded && mWorkspaceLoaded) { + && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) { mLoaderTask.runBindSynchronousPage(synchronousBindPage); } else { sWorkerThread.setPriority(Thread.NORM_PRIORITY); @@ -1484,22 +1474,16 @@ public class LauncherModel extends BroadcastReceiver */ private class LoaderTask implements Runnable { private Context mContext; - private boolean mIsLaunching; @Thunk boolean mIsLoadingAndBindingWorkspace; private boolean mStopped; @Thunk boolean mLoadAndBindStepFinished; private int mFlags; - LoaderTask(Context context, boolean isLaunching, int flags) { + LoaderTask(Context context, int flags) { mContext = context; - mIsLaunching = isLaunching; mFlags = flags; } - boolean isLaunching() { - return mIsLaunching; - } - boolean isLoadingWorkspace() { return mIsLoadingAndBindingWorkspace; } @@ -1600,20 +1584,15 @@ public class LauncherModel extends BroadcastReceiver public void run() { synchronized (mLock) { + if (mStopped) { + return; + } mIsLoaderTaskRunning = true; } // Optimize for end-user experience: if the Launcher is up and // running with the // All Apps interface in the foreground, load All Apps first. Otherwise, load the // workspace first (default). keep_running: { - // Elevate priority when Home launches for the first time to avoid - // starving at boot time. Staring at a blank home is not cool. - synchronized (mLock) { - if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " + - (mIsLaunching ? "DEFAULT" : "BACKGROUND")); - android.os.Process.setThreadPriority(mIsLaunching - ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND); - } if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace"); loadAndBindWorkspace(); @@ -1621,24 +1600,11 @@ public class LauncherModel extends BroadcastReceiver break keep_running; } - // Whew! Hard work done. Slow us down, and wait until the UI thread has - // settled down. - synchronized (mLock) { - if (mIsLaunching) { - if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND"); - android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - } - } waitForIdle(); // second step if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); loadAndBindAllApps(); - - // Restore the default thread priority after we are done loading items - synchronized (mLock) { - android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); - } } // Clear out this reference, otherwise we end up holding it until all of the @@ -2783,6 +2749,11 @@ public class LauncherModel extends BroadcastReceiver } if (!mAllAppsLoaded) { loadAllApps(); + synchronized (LoaderTask.this) { + if (mStopped) { + return; + } + } updateAllAppsIconsCache(); synchronized (LoaderTask.this) { if (mStopped) { @@ -2974,7 +2945,6 @@ public class LauncherModel extends BroadcastReceiver public void dumpState() { synchronized (sBgLock) { Log.d(TAG, "mLoaderTask.mContext=" + mContext); - Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching); Log.d(TAG, "mLoaderTask.mStopped=" + mStopped); Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished); Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size()); @@ -3694,39 +3664,6 @@ public class LauncherModel extends BroadcastReceiver return folderInfo; } - public static class WidgetAndShortcutNameComparator implements Comparator<Object> { - private final AppWidgetManagerCompat mManager; - private final PackageManager mPackageManager; - private final HashMap<Object, String> mLabelCache; - private final Collator mCollator; - - public WidgetAndShortcutNameComparator(Context context) { - mManager = AppWidgetManagerCompat.getInstance(context); - mPackageManager = context.getPackageManager(); - mLabelCache = new HashMap<Object, String>(); - mCollator = Collator.getInstance(); - } - public final int compare(Object a, Object b) { - String labelA, labelB; - if (mLabelCache.containsKey(a)) { - labelA = mLabelCache.get(a); - } else { - labelA = (a instanceof LauncherAppWidgetProviderInfo) - ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) a)) - : Utilities.trim(((ResolveInfo) a).loadLabel(mPackageManager)); - mLabelCache.put(a, labelA); - } - if (mLabelCache.containsKey(b)) { - labelB = mLabelCache.get(b); - } else { - labelB = (b instanceof LauncherAppWidgetProviderInfo) - ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) b)) - : Utilities.trim(((ResolveInfo) b).loadLabel(mPackageManager)); - mLabelCache.put(b, labelB); - } - return mCollator.compare(labelA, labelB); - } - }; static boolean isValidProvider(AppWidgetProviderInfo provider) { return (provider != null) && (provider.provider != null) diff --git a/src/com/android/launcher3/model/AppNameComparator.java b/src/com/android/launcher3/model/AppNameComparator.java new file mode 100644 index 000000000..706f7515d --- /dev/null +++ b/src/com/android/launcher3/model/AppNameComparator.java @@ -0,0 +1,105 @@ +package com.android.launcher3.model; + +import android.content.Context; + +import com.android.launcher3.AppInfo; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.compat.UserManagerCompat; +import java.text.Collator; +import java.util.Comparator; +import java.util.HashMap; + +/** + * Class to manage access to an app name comparator. + * <p> + * Used to sort application name in all apps view and widget tray view. + */ +public class AppNameComparator { + private final UserManagerCompat mUserManager; + private final Collator mCollator; + private final Comparator<ItemInfo> mAppInfoComparator; + private final Comparator<String> mSectionNameComparator; + private HashMap<UserHandleCompat, Long> mUserSerialCache = new HashMap<>(); + + public AppNameComparator(Context context) { + mCollator = Collator.getInstance(); + mUserManager = UserManagerCompat.getInstance(context); + mAppInfoComparator = new Comparator<ItemInfo>() { + + public final int compare(ItemInfo a, ItemInfo b) { + // Order by the title in the current locale + int result = compareTitles(a.title.toString(), b.title.toString()); + if (result == 0 && a instanceof AppInfo && b instanceof AppInfo) { + AppInfo aAppInfo = (AppInfo) a; + AppInfo bAppInfo = (AppInfo) b; + // If two apps have the same title, then order by the component name + result = aAppInfo.componentName.compareTo(bAppInfo.componentName); + if (result == 0) { + // If the two apps are the same component, then prioritize by the order that + // the app user was created (prioritizing the main user's apps) + if (UserHandleCompat.myUserHandle().equals(a.user)) { + return -1; + } else { + Long aUserSerial = getAndCacheUserSerial(a.user); + Long bUserSerial = getAndCacheUserSerial(b.user); + return aUserSerial.compareTo(bUserSerial); + } + } + } + return result; + } + }; + mSectionNameComparator = new Comparator<String>() { + @Override + public int compare(String o1, String o2) { + return compareTitles(o1, o2); + } + }; + } + + /** + * Returns a locale-aware comparator that will alphabetically order a list of applications. + */ + public Comparator<ItemInfo> getAppInfoComparator() { + // Clear the user serial cache so that we get serials as needed in the comparator + mUserSerialCache.clear(); + return mAppInfoComparator; + } + + /** + * Returns a locale-aware comparator that will alphabetically order a list of section names. + */ + public Comparator<String> getSectionNameComparator() { + return mSectionNameComparator; + } + + /** + * Compares two titles with the same return value semantics as Comparator. + */ + private int compareTitles(String titleA, String titleB) { + // Ensure that we de-prioritize any titles that don't start with a linguistic letter or digit + boolean aStartsWithLetter = Character.isLetterOrDigit(titleA.codePointAt(0)); + boolean bStartsWithLetter = Character.isLetterOrDigit(titleB.codePointAt(0)); + if (aStartsWithLetter && !bStartsWithLetter) { + return -1; + } else if (!aStartsWithLetter && bStartsWithLetter) { + return 1; + } + + // Order by the title in the current locale + return mCollator.compare(titleA, titleB); + } + + /** + * Returns the user serial for this user, using a cached serial if possible. + */ + private Long getAndCacheUserSerial(UserHandleCompat user) { + Long userSerial = mUserSerialCache.get(user); + if (userSerial == null) { + userSerial = mUserManager.getSerialNumberForUser(user); + mUserSerialCache.put(user, userSerial); + } + return userSerial; + } +} diff --git a/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java new file mode 100644 index 000000000..7c4e80651 --- /dev/null +++ b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java @@ -0,0 +1,63 @@ +package com.android.launcher3.model; + +import android.appwidget.AppWidgetProviderInfo; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; + +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.Utilities; +import com.android.launcher3.compat.AppWidgetManagerCompat; + +import java.text.Collator; +import java.util.Comparator; +import java.util.HashMap; + +public class WidgetsAndShortcutNameComparator implements Comparator<Object> { + private final AppWidgetManagerCompat mManager; + private final PackageManager mPackageManager; + private final HashMap<Object, String> mLabelCache; + private final Collator mCollator; + + public WidgetsAndShortcutNameComparator(Context context) { + mManager = AppWidgetManagerCompat.getInstance(context); + mPackageManager = context.getPackageManager(); + mLabelCache = new HashMap<Object, String>(); + mCollator = Collator.getInstance(); + } + + @Override + public final int compare(Object a, Object b) { + String labelA, labelB; + if (mLabelCache.containsKey(a)) { + labelA = mLabelCache.get(a); + } else { + labelA = (a instanceof LauncherAppWidgetProviderInfo) + ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) a)) + : Utilities.trim(((ResolveInfo) a).loadLabel(mPackageManager)); + mLabelCache.put(a, labelA); + } + if (mLabelCache.containsKey(b)) { + labelB = mLabelCache.get(b); + } else { + labelB = (b instanceof LauncherAppWidgetProviderInfo) + ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) b)) + : Utilities.trim(((ResolveInfo) b).loadLabel(mPackageManager)); + mLabelCache.put(b, labelB); + } + int result = mCollator.compare(labelA, labelB); + if (result == 0 && a instanceof AppWidgetProviderInfo && + b instanceof AppWidgetProviderInfo) { + AppWidgetProviderInfo aInfo = (AppWidgetProviderInfo) a; + AppWidgetProviderInfo bInfo = (AppWidgetProviderInfo) b; + + // prioritize main user's widgets against work profile widgets. + if (aInfo.getProfile().equals(android.os.Process.myUserHandle())) { + return -1; + } else if (bInfo.getProfile().equals(android.os.Process.myUserHandle())) { + return 1; + } + } + return result; + } +}; diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index e3eb76c25..dcaf1f211 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -21,17 +21,14 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.View.OnLayoutChangeListener; -import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetProviderInfo; @@ -41,7 +38,13 @@ import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest; import com.android.launcher3.compat.AppWidgetManagerCompat; /** - * Represents the individual cell of the widget inside the widget tray. + * Represents the individual cell of the widget inside the widget tray. The preview is drawn + * horizontally centered, and scaled down if needed. + * + * This view does not support padding. Since the image is scaled down to fit the view, padding will + * further decrease the scaling factor. Drag-n-drop uses the view bounds for showing a smooth + * transition from the view to drag view, so when adding padding support, DnD would need to + * consider the appropriate scaling factor. */ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { @@ -59,13 +62,11 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { private int mPresetPreviewSize; int cellSize; - private ImageView mWidgetImage; + private WidgetImageView mWidgetImage; private TextView mWidgetName; private TextView mWidgetDims; - private final Rect mOrigImgPadding = new Rect(); private String mDimensionsFormatString; - private boolean mIsAppWidget; private Object mInfo; private WidgetPreviewLoader mWidgetPreviewLoader; @@ -101,12 +102,7 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { protected void onFinishInflate() { super.onFinishInflate(); - mWidgetImage = (ImageView) findViewById(R.id.widget_preview); - mOrigImgPadding.left = mWidgetImage.getPaddingLeft(); - mOrigImgPadding.top = mWidgetImage.getPaddingTop(); - mOrigImgPadding.right = mWidgetImage.getPaddingRight(); - mOrigImgPadding.bottom = mWidgetImage.getPaddingBottom(); - + mWidgetImage = (WidgetImageView) findViewById(R.id.widget_preview); mWidgetName = ((TextView) findViewById(R.id.widget_name)); mWidgetDims = ((TextView) findViewById(R.id.widget_dims)); } @@ -119,7 +115,7 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { Log.d(TAG, "reset called on:" + mWidgetName.getText()); } mWidgetImage.animate().cancel(); - mWidgetImage.setImageDrawable(null); + mWidgetImage.setBitmap(null); mWidgetName.setText(null); mWidgetDims.setText(null); @@ -133,15 +129,11 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { * Apply the widget provider info to the view. */ public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info, - int maxWidth, WidgetPreviewLoader loader) { + WidgetPreviewLoader loader) { LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); - mIsAppWidget = true; mInfo = info; - if (maxWidth > -1) { - mWidgetImage.setMaxWidth(maxWidth); - } // TODO(hyunyoungs): setup a cache for these labels. mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info)); int hSpan = Math.min(info.spanX, grid.numColumns); @@ -155,7 +147,6 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { */ public void applyFromResolveInfo( PackageManager pm, ResolveInfo info, WidgetPreviewLoader loader) { - mIsAppWidget = false; mInfo = info; CharSequence label = info.loadLabel(pm); mWidgetName.setText(label); @@ -172,20 +163,8 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { } public void applyPreview(Bitmap bitmap) { - FastBitmapDrawable preview = new FastBitmapDrawable(bitmap); - - if (preview != null) { - mWidgetImage.setImageDrawable(preview); - - if (mIsAppWidget) { - // center horizontally - int[] imageSize = getPreviewSize(); - int centerAmount = (imageSize[0] - preview.getIntrinsicWidth()) / 2; - mWidgetImage.setPadding(mOrigImgPadding.left + centerAmount, - mOrigImgPadding.top, - mOrigImgPadding.right, - mOrigImgPadding.bottom); - } + if (bitmap != null) { + mWidgetImage.setBitmap(bitmap); mWidgetImage.setAlpha(0f); mWidgetImage.animate().alpha(1.0f).setDuration(FADE_IN_DURATION_MS); } diff --git a/src/com/android/launcher3/widget/WidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java index f1eaf6488..6f8fd897b 100644 --- a/src/com/android/launcher3/widget/WidgetImageView.java +++ b/src/com/android/launcher3/widget/WidgetImageView.java @@ -17,25 +17,73 @@ package com.android.launcher3.widget; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; import android.util.AttributeSet; -import android.widget.ImageView; +import android.view.View; -public class WidgetImageView extends ImageView { +/** + * View that draws a bitmap horizontally centered. If the image width is greater than the view + * width, the image is scaled down appropriately. + */ +public class WidgetImageView extends View { + + private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private final RectF mDstRectF = new RectF(); + private Bitmap mBitmap; + + public WidgetImageView(Context context) { + super(context); + } public WidgetImageView(Context context, AttributeSet attrs) { super(context, attrs); } + public WidgetImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setBitmap(Bitmap bitmap) { + mBitmap = bitmap; + invalidate(); + } + + public Bitmap getBitmap() { + return mBitmap; + } + @Override protected void onDraw(Canvas canvas) { - canvas.save(); - canvas.clipRect(getScrollX() + getPaddingLeft(), - getScrollY() + getPaddingTop(), - getScrollX() + getRight() - getLeft() - getPaddingRight(), - getScrollY() + getBottom() - getTop() - getPaddingBottom()); - - super.onDraw(canvas); - canvas.restore(); + if (mBitmap != null) { + updateDstRectF(); + canvas.drawBitmap(mBitmap, null, mDstRectF, mPaint); + } + } + + private void updateDstRectF() { + if (mBitmap.getWidth() > getWidth()) { + float scale = ((float) getWidth()) / mBitmap.getWidth(); + mDstRectF.set(0, 0, getWidth(), scale * mBitmap.getHeight()); + } else { + mDstRectF.set( + (getWidth() - mBitmap.getWidth()) * 0.5f, + 0, + (getWidth() + mBitmap.getWidth()) * 0.5f, + mBitmap.getHeight()); + } + } + + /** + * @return the bounds where the image was drawn. + */ + public Rect getBitmapBounds() { + updateDstRectF(); + Rect rect = new Rect(); + mDstRectF.round(rect); + return rect; } } diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 00fb225ec..181c08a40 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -19,7 +19,6 @@ package com.android.launcher3.widget; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Bitmap; -import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.v7.widget.LinearLayoutManager; @@ -28,8 +27,8 @@ import android.support.v7.widget.RecyclerView.State; import android.util.AttributeSet; import android.util.Log; import android.view.View; -import android.widget.ImageView; import android.widget.Toast; + import com.android.launcher3.BaseContainerView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeleteDropTarget; @@ -37,10 +36,8 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragController; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget.DragObject; -import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.Folder; import com.android.launcher3.IconCache; -import com.android.launcher3.Insettable; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; @@ -222,20 +219,19 @@ public class WidgetsContainerView extends BaseContainerView private boolean beginDraggingWidget(WidgetCell v) { // Get the widget preview as the drag representation - ImageView image = (ImageView) v.findViewById(R.id.widget_preview); + WidgetImageView image = (WidgetImageView) v.findViewById(R.id.widget_preview); PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag(); // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and // we abort the drag. - if (image.getDrawable() == null) { + if (image.getBitmap() == null) { return false; } // Compose the drag image Bitmap preview; - Bitmap outline; float scale = 1f; - Point previewPadding = null; + final Rect bounds = image.getBitmapBounds(); if (createItemInfo instanceof PendingAddWidgetInfo) { // This can happen in some weird cases involving multi-touch. We can't start dragging @@ -244,25 +240,25 @@ public class WidgetsContainerView extends BaseContainerView PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo; int[] size = mLauncher.getWorkspace().estimateItemSize(createWidgetInfo, true); - FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable(); + Bitmap icon = image.getBitmap(); float minScale = 1.25f; - int maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]); + int maxWidth = Math.min((int) (icon.getWidth() * minScale), size[0]); int[] previewSizeBeforeScale = new int[1]; preview = getWidgetPreviewLoader().generateWidgetPreview(createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale); - // Compare the size of the drag preview to the preview in the AppsCustomize tray - int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0], - v.getActualItemWidth()); - scale = previewWidthInAppsCustomize / (float) preview.getWidth(); - - // The bitmap in the AppsCustomize tray is always the the same size, so there - // might be extra pixels around the preview itself - this accounts for that - if (previewWidthInAppsCustomize < previewDrawable.getIntrinsicWidth()) { - int padding = - (previewDrawable.getIntrinsicWidth() - previewWidthInAppsCustomize) / 2; - previewPadding = new Point(padding, 0); + + if (previewSizeBeforeScale[0] < icon.getWidth()) { + // The icon has extra padding around it. + int padding = (icon.getWidth() - previewSizeBeforeScale[0]) / 2; + if (icon.getWidth() > image.getWidth()) { + padding = padding * image.getWidth() / icon.getWidth(); + } + + bounds.left += padding; + bounds.right -= padding; } + scale = bounds.width() / (float) preview.getWidth(); } else { PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag(); Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.activityInfo); @@ -274,16 +270,12 @@ public class WidgetsContainerView extends BaseContainerView boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo && (((PendingAddWidgetInfo) createItemInfo).previewImage == 0)); - // Save the preview for the outline generation, then dim the preview - outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(), - false); - // Start the drag mLauncher.lockScreenOrientation(); - mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, clipAlpha); + mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, preview, clipAlpha); mDragController.startDrag(image, preview, this, createItemInfo, - DragController.DRAG_ACTION_COPY, previewPadding, scale); - outline.recycle(); + bounds, DragController.DRAG_ACTION_COPY, scale); + preview.recycle(); return true; } diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java index 397d17799..2f733dcbc 100644 --- a/src/com/android/launcher3/widget/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java @@ -18,9 +18,9 @@ package com.android.launcher3.widget; import android.annotation.TargetApi; import android.content.Context; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.os.Build; import android.support.v7.widget.RecyclerView; -import android.content.res.Resources; import android.support.v7.widget.RecyclerView.Adapter; import android.util.Log; import android.view.LayoutInflater; @@ -29,15 +29,16 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; import android.widget.LinearLayout; + import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DynamicGrid; -import com.android.launcher3.IconCache; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.R; import com.android.launcher3.WidgetPreviewLoader; + import java.util.List; /** @@ -56,7 +57,6 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { private Context mContext; private Launcher mLauncher; private LayoutInflater mLayoutInflater; - private IconCache mIconCache; private WidgetsModel mWidgetsModel; private WidgetPreviewLoader mWidgetPreviewLoader; @@ -76,9 +76,7 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; - mLauncher = launcher; - mIconCache = LauncherAppState.getInstance().getIconCache(); setContainerHeight(); } @@ -143,7 +141,7 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) infoList.get(i); PendingAddWidgetInfo pawi = new PendingAddWidgetInfo(info, null); widget.setTag(pawi); - widget.applyFromAppWidgetProviderInfo(info, -1, mWidgetPreviewLoader); + widget.applyFromAppWidgetProviderInfo(info, mWidgetPreviewLoader); } else if (infoList.get(i) instanceof ResolveInfo) { ResolveInfo info = (ResolveInfo) infoList.get(i); PendingAddShortcutInfo pasi = new PendingAddShortcutInfo(info.activityInfo); diff --git a/src/com/android/launcher3/widget/WidgetsModel.java b/src/com/android/launcher3/widget/WidgetsModel.java index 71a7b9446..5a920e8d4 100644 --- a/src/com/android/launcher3/widget/WidgetsModel.java +++ b/src/com/android/launcher3/widget/WidgetsModel.java @@ -10,8 +10,9 @@ import android.util.Log; import com.android.launcher3.IconCache; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetProviderInfo; -import com.android.launcher3.LauncherModel.WidgetAndShortcutNameComparator; import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.model.AppNameComparator; +import com.android.launcher3.model.WidgetsAndShortcutNameComparator; import java.util.ArrayList; import java.util.Collections; @@ -40,12 +41,14 @@ public class WidgetsModel { private RecyclerView.Adapter mAdapter; private Comparator mWidgetAndShortcutNameComparator; + private Comparator mAppNameComparator; private IconCache mIconCache; public WidgetsModel(Context context, RecyclerView.Adapter adapter) { mAdapter = adapter; - mWidgetAndShortcutNameComparator = new WidgetAndShortcutNameComparator(context); + mWidgetAndShortcutNameComparator = new WidgetsAndShortcutNameComparator(context); + mAppNameComparator = (new AppNameComparator(context)).getAppInfoComparator(); mIconCache = LauncherAppState.getInstance().getIconCache(); } @@ -108,7 +111,7 @@ public class WidgetsModel { } // sort. - sortPackageItemInfos(); + Collections.sort(mPackageItemInfos, mAppNameComparator); for (PackageItemInfo p: mPackageItemInfos) { Collections.sort(mWidgetsList.get(p), mWidgetAndShortcutNameComparator); } @@ -116,13 +119,4 @@ public class WidgetsModel { // notify. mAdapter.notifyDataSetChanged(); } - - private void sortPackageItemInfos() { - Collections.sort(mPackageItemInfos, new Comparator<PackageItemInfo>() { - @Override - public int compare(PackageItemInfo lhs, PackageItemInfo rhs) { - return lhs.title.toString().compareTo(rhs.title.toString()); - } - }); - } }
\ No newline at end of file |