diff options
Diffstat (limited to 'src/com/android')
24 files changed, 1435 insertions, 1266 deletions
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index dd646bb22..3b25dca34 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -24,6 +24,7 @@ import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserHandleCompat; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; @@ -117,6 +118,16 @@ class AllAppsList { } } + public void updateIconsAndLabels(HashSet<String> packages, UserHandleCompat user, + ArrayList<AppInfo> outUpdates) { + for (AppInfo info : data) { + if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) { + mIconCache.updateTitleAndIcon(info); + outUpdates.add(info); + } + } + } + /** * Add and remove icons for this package which has been updated. */ diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java index c7ee2e99a..70e36a744 100644 --- a/src/com/android/launcher3/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/AlphabeticalAppsList.java @@ -82,15 +82,30 @@ public class AlphabeticalAppsList { * Info about a section in the alphabetic list */ public static class SectionInfo { - // The name of this section - public String sectionName; // The number of applications in this section - public int numAppsInSection; + public int numApps; + // The section break AdapterItem for this section + public AdapterItem sectionBreakItem; // The first app AdapterItem for this section public AdapterItem firstAppItem; + } - public SectionInfo(String name) { - sectionName = name; + /** + * Info about a fast scroller section, depending if sections are merged, the fast scroller + * sections will not be the same set as the section headers. + */ + public static class FastScrollSectionInfo { + // The section name + public String sectionName; + // To map the touch (from 0..1) to the index in the app list to jump to in the fast + // scroller, we use the fraction in range (0..1) of the app index / total app count. + public float appRangeFraction; + // The AdapterItem to scroll to for this section + public AdapterItem appItem; + + public FastScrollSectionInfo(String sectionName, float appRangeFraction) { + this.sectionName = sectionName; + this.appRangeFraction = appRangeFraction; } } @@ -98,32 +113,41 @@ public class AlphabeticalAppsList { * Info about a particular adapter item (can be either section or app) */ public static class AdapterItem { + /** Section & App properties */ // The index of this adapter item in the list public int position; // Whether or not the item at this adapter position is a section or not public boolean isSectionHeader; - // The name of this section, or the section that this app is contained in - public String sectionName; - // The associated AppInfo, or null if this adapter item is a section - public AppInfo appInfo; - // The index of this app (not including sections), or -1 if this adapter item is a section - public int appIndex; - - public static AdapterItem asSection(int pos, String name) { + // The section for this item + public SectionInfo sectionInfo; + + /** App-only properties */ + // The section name of this app. Note that there can be multiple items with different + // sectionNames in the same section + public String sectionName = null; + // The index of this app in the section + public int sectionAppIndex = -1; + // The associated AppInfo for the app + public AppInfo appInfo = null; + // The index of this app not including sections + public int appIndex = -1; + + public static AdapterItem asSectionBreak(int pos, SectionInfo section) { AdapterItem item = new AdapterItem(); item.position = pos; item.isSectionHeader = true; - item.sectionName = name; - item.appInfo = null; - item.appIndex = -1; + item.sectionInfo = section; return item; } - public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo, int appIndex) { + public static AdapterItem asApp(int pos, SectionInfo section, String sectionName, + int sectionAppIndex, AppInfo appInfo, int appIndex) { AdapterItem item = new AdapterItem(); item.position = pos; item.isSectionHeader = false; + item.sectionInfo = section; item.sectionName = sectionName; + item.sectionAppIndex = sectionAppIndex; item.appInfo = appInfo; item.appIndex = appIndex; return item; @@ -137,18 +161,35 @@ public class AlphabeticalAppsList { public boolean retainApp(AppInfo info, String sectionName); } + // The maximum number of rows allowed in a merged section before we stop merging + private static final int MAX_ROWS_IN_MERGED_SECTION = 3; + private List<AppInfo> mApps = new ArrayList<>(); private List<AppInfo> mFilteredApps = new ArrayList<>(); private List<AdapterItem> mSectionedFilteredApps = new ArrayList<>(); private List<SectionInfo> mSections = new ArrayList<>(); + private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>(); private RecyclerView.Adapter mAdapter; private Filter mFilter; private AlphabeticIndexCompat mIndexer; private AppNameComparator mAppNameComparator; + private int mNumAppsPerRow; + // The maximum number of section merges we allow at a given time before we stop merging + private int mMaxAllowableMerges = Integer.MAX_VALUE; - public AlphabeticalAppsList(Context context) { + public AlphabeticalAppsList(Context context, int numAppsPerRow) { mIndexer = new AlphabeticIndexCompat(context); mAppNameComparator = new AppNameComparator(context); + setNumAppsPerRow(numAppsPerRow); + } + + /** + * Sets the number of apps per row. Used only for AppsContainerView.SECTIONED_GRID_COALESCED. + */ + public void setNumAppsPerRow(int numAppsPerRow) { + mNumAppsPerRow = numAppsPerRow; + mMaxAllowableMerges = (int) Math.ceil(numAppsPerRow / 2f); + onAppsUpdated(); } /** @@ -166,6 +207,13 @@ public class AlphabeticalAppsList { } /** + * Returns fast scroller sections of all the current filtered applications. + */ + public List<FastScrollSectionInfo> getFastScrollerSections() { + return mFastScrollerSections; + } + + /** * Returns the current filtered list of applications broken down into their sections. */ public List<AdapterItem> getAdapterItems() { @@ -180,6 +228,13 @@ public class AlphabeticalAppsList { } /** + * Returns whether there are is a filter set. + */ + public boolean hasFilter() { + return (mFilter != null); + } + + /** * Returns whether there are no filtered results. */ public boolean hasNoFilteredResults() { @@ -190,16 +245,17 @@ public class AlphabeticalAppsList { * Sets the current filter for this list of apps. */ public void setFilter(Filter f) { - mFilter = f; - onAppsUpdated(); - mAdapter.notifyDataSetChanged(); + if (mFilter != f) { + mFilter = f; + onAppsUpdated(); + mAdapter.notifyDataSetChanged(); + } } /** * Sets the current set of apps. */ public void setApps(List<AppInfo> apps) { - Collections.sort(apps, mAppNameComparator.getComparator()); mApps.clear(); mApps.addAll(apps); onAppsUpdated(); @@ -214,6 +270,8 @@ public class AlphabeticalAppsList { for (AppInfo info : apps) { addApp(info); } + onAppsUpdated(); + mAdapter.notifyDataSetChanged(); } /** @@ -224,12 +282,12 @@ public class AlphabeticalAppsList { int index = mApps.indexOf(info); if (index != -1) { mApps.set(index, info); - onAppsUpdated(); - mAdapter.notifyItemChanged(index); } else { addApp(info); } } + onAppsUpdated(); + mAdapter.notifyDataSetChanged(); } /** @@ -240,10 +298,10 @@ public class AlphabeticalAppsList { int removeIndex = findAppByComponent(mApps, info); if (removeIndex != -1) { mApps.remove(removeIndex); - onAppsUpdated(); - mAdapter.notifyDataSetChanged(); } } + onAppsUpdated(); + mAdapter.notifyDataSetChanged(); } /** @@ -263,14 +321,12 @@ public class AlphabeticalAppsList { } /** - * Implementation to actually add an app to the alphabetic list + * Implementation to actually add an app to the alphabetic list, but does not notify. */ private void addApp(AppInfo info) { int index = Collections.binarySearch(mApps, info, mAppNameComparator.getComparator()); if (index < 0) { mApps.add(-(index + 1), info); - onAppsUpdated(); - mAdapter.notifyDataSetChanged(); } } @@ -278,13 +334,20 @@ public class AlphabeticalAppsList { * Updates internals when the set of apps are updated. */ private void onAppsUpdated() { + // Sort the list of apps + Collections.sort(mApps, mAppNameComparator.getComparator()); + // Recreate the filtered and sectioned apps (for convenience for the grid layout) mFilteredApps.clear(); mSections.clear(); mSectionedFilteredApps.clear(); + mFastScrollerSections.clear(); SectionInfo lastSectionInfo = null; + String lastSectionName = null; + FastScrollSectionInfo lastFastScrollerSectionInfo = null; int position = 0; int appIndex = 0; + int numApps = mApps.size(); for (AppInfo info : mApps) { String sectionName = mIndexer.computeSectionName(info.title.toString().trim()); @@ -294,23 +357,78 @@ public class AlphabeticalAppsList { } // Create a new section if necessary - if (lastSectionInfo == null || !lastSectionInfo.sectionName.equals(sectionName)) { - lastSectionInfo = new SectionInfo(sectionName); + if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) { + lastSectionName = sectionName; + lastSectionInfo = new SectionInfo(); mSections.add(lastSectionInfo); - - // Create a new section item - AdapterItem sectionItem = AdapterItem.asSection(position++, sectionName); - mSectionedFilteredApps.add(sectionItem); + lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName, + (float) appIndex / numApps); + mFastScrollerSections.add(lastFastScrollerSectionInfo); + + // Create a new section item, this item is used to break the flow of items in the + // list + AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo); + if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS && !hasFilter()) { + lastSectionInfo.sectionBreakItem = sectionItem; + mSectionedFilteredApps.add(sectionItem); + } } // Create an app item - AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++); - lastSectionInfo.numAppsInSection++; + AdapterItem appItem = AdapterItem.asApp(position++, lastSectionInfo, sectionName, + lastSectionInfo.numApps++, info, appIndex++); if (lastSectionInfo.firstAppItem == null) { lastSectionInfo.firstAppItem = appItem; + lastFastScrollerSectionInfo.appItem = appItem; } mSectionedFilteredApps.add(appItem); mFilteredApps.add(info); } + + if (AppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) { + // Go through each section and try and merge some of the sections + int minNumAppsPerRow = (int) Math.ceil(mNumAppsPerRow / 2f); + int sectionAppCount = 0; + for (int i = 0; i < mSections.size(); i++) { + SectionInfo section = mSections.get(i); + sectionAppCount = section.numApps; + int mergeCount = 1; + + // Merge rows if the last app in this section is in a column that is greater than + // 0, but less than the min number of apps per row. In addition, apply the + // constraint to stop merging if the number of rows in the section is greater than + // some limit, and also if there are no lessons to merge. + while (0 < (sectionAppCount % mNumAppsPerRow) && + (sectionAppCount % mNumAppsPerRow) < minNumAppsPerRow && + (sectionAppCount / mNumAppsPerRow) < MAX_ROWS_IN_MERGED_SECTION && + (i + 1) < mSections.size()) { + SectionInfo nextSection = mSections.remove(i + 1); + + // Remove the next section break + mSectionedFilteredApps.remove(nextSection.sectionBreakItem); + int pos = mSectionedFilteredApps.indexOf(section.firstAppItem); + // Point the section for these new apps to the merged section + int nextPos = pos + section.numApps; + for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) { + AdapterItem item = mSectionedFilteredApps.get(j); + item.sectionInfo = section; + item.sectionAppIndex += section.numApps; + } + + // Update the following adapter items of the removed section item + pos = mSectionedFilteredApps.indexOf(nextSection.firstAppItem); + for (int j = pos; j < mSectionedFilteredApps.size(); j++) { + AdapterItem item = mSectionedFilteredApps.get(j); + item.position--; + } + section.numApps += nextSection.numApps; + sectionAppCount += nextSection.numApps; + mergeCount++; + if (mergeCount >= mMaxAllowableMerges) { + break; + } + } + } + } } } diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java index f8897128e..6556cf920 100644 --- a/src/com/android/launcher3/AppsContainerRecyclerView.java +++ b/src/com/android/launcher3/AppsContainerRecyclerView.java @@ -36,8 +36,7 @@ import java.util.List; * A RecyclerView with custom fastscroll support. This is the main container for the all apps * icons. */ -public class AppsContainerRecyclerView extends RecyclerView - implements RecyclerView.OnItemTouchListener { +public class AppsContainerRecyclerView extends BaseContainerRecyclerView { private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f; @@ -132,6 +131,7 @@ public class AppsContainerRecyclerView extends RecyclerView @Override protected void onFinishInflate() { + super.onFinishInflate(); addOnItemTouchListener(this); } @@ -156,10 +156,6 @@ public class AppsContainerRecyclerView extends RecyclerView handleTouchEvent(ev); } - public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { - // Do nothing - } - /** * Handles the touch event and determines whether to show the fast scroller (or updates it if * it is already showing). @@ -175,7 +171,9 @@ public class AppsContainerRecyclerView extends RecyclerView // Keep track of the down positions mDownX = mLastX = x; mDownY = mLastY = y; - stopScroll(); + if (shouldStopScroll(ev)) { + stopScroll(); + } break; case MotionEvent.ACTION_MOVE: // Check if we are scrolling @@ -256,8 +254,9 @@ public class AppsContainerRecyclerView extends RecyclerView mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255)); mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0, mFastScrollSectionName.length(), mFastScrollTextBounds); + float textWidth = mFastScrollTextPaint.measureText(mFastScrollSectionName); canvas.drawText(mFastScrollSectionName, - (bgBounds.width() - mFastScrollTextBounds.width()) / 2, + (bgBounds.width() - textWidth) / 2, bgBounds.height() - (bgBounds.height() - mFastScrollTextBounds.height()) / 2, mFastScrollTextPaint); canvas.restoreToCount(restoreCount); @@ -287,45 +286,43 @@ public class AppsContainerRecyclerView extends RecyclerView } /** - * Maps the progress (from 0..1) to the position that should be visible + * Maps the touch (from 0..1) to the adapter position that should be visible. */ - private String scrollToPositionAtProgress(float progress) { - List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections(); - if (sections.isEmpty()) { + private String scrollToPositionAtProgress(float touchFraction) { + // Ensure that we have any sections + List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections = + mApps.getFastScrollerSections(); + if (fastScrollSections.isEmpty()) { return ""; } - // Find the position of the first application in the section that contains the row at the - // current progress - int rowAtProgress = (int) (progress * getNumRows()); - int rowCount = 0; - AlphabeticalAppsList.SectionInfo lastSectionInfo = null; - for (AlphabeticalAppsList.SectionInfo section : sections) { - int numRowsInSection = (int) Math.ceil((float) section.numAppsInSection / mNumAppsPerRow); - if (rowCount + numRowsInSection >= rowAtProgress) { - lastSectionInfo = section; + AlphabeticalAppsList.FastScrollSectionInfo lastScrollSection = fastScrollSections.get(0); + for (int i = 1; i < fastScrollSections.size(); i++) { + AlphabeticalAppsList.FastScrollSectionInfo scrollSection = fastScrollSections.get(i); + if (lastScrollSection.appRangeFraction <= touchFraction && + touchFraction < scrollSection.appRangeFraction) { break; } - rowCount += numRowsInSection; + lastScrollSection = scrollSection; } - int position = mApps.getAdapterItems().indexOf(lastSectionInfo.firstAppItem); // Scroll the position into view, anchored at the top of the screen if possible. We call the // scroll method on the LayoutManager directly since it is not exposed by RecyclerView. LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager(); stopScroll(); - layoutManager.scrollToPositionWithOffset(position, 0); + layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0); - // Return the section name of the row - return mApps.getAdapterItems().get(position).sectionName; + return lastScrollSection.sectionName; } /** * Returns the bounds for the scrollbar. */ private void updateVerticalScrollbarBounds() { + List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems(); + // Skip early if there are no items - if (mApps.getAdapterItems().isEmpty()) { + if (items.isEmpty()) { mVerticalScrollbarBounds.setEmpty(); return; } @@ -344,7 +341,7 @@ public class AppsContainerRecyclerView extends RecyclerView View child = getChildAt(i); int position = getChildPosition(child); if (position != NO_POSITION) { - AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position); + AlphabeticalAppsList.AdapterItem item = items.get(position); if (!item.isSectionHeader) { rowIndex = findRowForAppIndex(item.appIndex); rowTopOffset = getLayoutManager().getDecoratedTop(child); @@ -391,11 +388,11 @@ public class AppsContainerRecyclerView extends RecyclerView int appIndex = 0; int rowCount = 0; for (AlphabeticalAppsList.SectionInfo info : sections) { - int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow); - if (appIndex + info.numAppsInSection > position) { + int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow); + if (appIndex + info.numApps > position) { return rowCount + ((position - appIndex) / mNumAppsPerRow); } - appIndex += info.numAppsInSection; + appIndex += info.numApps; rowCount += numRowsInSection; } return appIndex; @@ -408,7 +405,7 @@ public class AppsContainerRecyclerView extends RecyclerView List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections(); int rowCount = 0; for (AlphabeticalAppsList.SectionInfo info : sections) { - int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow); + int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow); rowCount += numRowsInSection; } return rowCount; diff --git a/src/com/android/launcher3/AppsContainerSearchEditTextView.java b/src/com/android/launcher3/AppsContainerSearchEditTextView.java new file mode 100644 index 000000000..c688237b2 --- /dev/null +++ b/src/com/android/launcher3/AppsContainerSearchEditTextView.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.widget.EditText; + + +/** + * The edit text for the search container + */ +public class AppsContainerSearchEditTextView extends EditText { + + /** + * Implemented by listeners of the back key. + */ + public interface OnBackKeyListener { + public void onBackKey(); + } + + private OnBackKeyListener mBackKeyListener; + + public AppsContainerSearchEditTextView(Context context) { + this(context, null); + } + + public AppsContainerSearchEditTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public AppsContainerSearchEditTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setOnBackKeyListener(OnBackKeyListener listener) { + mBackKeyListener = listener; + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + // If this is a back key, propagate the key back to the listener + if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) { + if (mBackKeyListener != null) { + mBackKeyListener.onBackKey(); + } + return false; + } + return super.onKeyPreIme(keyCode, event); + } +} diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java index f7adaf81d..993f9c857 100644 --- a/src/com/android/launcher3/AppsContainerView.java +++ b/src/com/android/launcher3/AppsContainerView.java @@ -31,48 +31,53 @@ import android.view.ViewConfiguration; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; -import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; - import com.android.launcher3.util.Thunk; import java.util.List; /** - * The all apps list view container. + * The all apps view container. */ -public class AppsContainerView extends FrameLayout implements DragSource, Insettable, TextWatcher, - TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener, - View.OnLongClickListener { +public class AppsContainerView extends BaseContainerView implements DragSource, Insettable, + TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener, + View.OnClickListener, View.OnLongClickListener { - private static final boolean ALLOW_SINGLE_APP_LAUNCH = true; + public static final boolean GRID_MERGE_SECTIONS = true; + public static final boolean GRID_HIDE_SECTION_HEADERS = false; - private static final int GRID_LAYOUT = 0; - private static final int LIST_LAYOUT = 1; - private static final int USE_LAYOUT = GRID_LAYOUT; + private static final boolean ALLOW_SINGLE_APP_LAUNCH = true; + private static final boolean DYNAMIC_HEADER_ELEVATION = false; + private static final boolean DISMISS_SEARCH_ON_BACK = true; + private static final float HEADER_ELEVATION_DP = 4; + private static final int FADE_IN_DURATION = 175; + private static final int FADE_OUT_DURATION = 100; + private static final int SEARCH_TRANSLATION_X_DP = 18; @Thunk Launcher mLauncher; @Thunk AlphabeticalAppsList mApps; - private RecyclerView.Adapter mAdapter; + private AppsGridAdapter mAdapter; private RecyclerView.LayoutManager mLayoutManager; private RecyclerView.ItemDecoration mItemDecoration; private LinearLayout mContentView; @Thunk AppsContainerRecyclerView mAppsRecyclerView; - private EditText mSearchBarView; - + private View mHeaderView; + private View mSearchBarContainerView; + private View mSearchButtonView; + private View mDismissSearchButtonView; + private AppsContainerSearchEditTextView mSearchBarEditView; + private int mNumAppsPerRow; private Point mLastTouchDownPos = new Point(-1, -1); private Point mLastTouchPos = new Point(); - private Rect mInsets = new Rect(); - private Rect mFixedBounds = new Rect(); private int mContentMarginStart; // Normal container insets private int mContainerInset; - // Fixed bounds container insets - private int mFixedBoundsContainerInset; + // RecyclerView scroll position + @Thunk int mRecyclerViewScrollY; public AppsContainerView(Context context) { this(context, null); @@ -90,26 +95,15 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett mContainerInset = context.getResources().getDimensionPixelSize( R.dimen.apps_container_inset); - mFixedBoundsContainerInset = context.getResources().getDimensionPixelSize( - R.dimen.apps_container_fixed_bounds_inset); mLauncher = (Launcher) context; - mApps = new AlphabeticalAppsList(context); - if (USE_LAYOUT == GRID_LAYOUT) { - mNumAppsPerRow = grid.appsViewNumCols; - AppsGridAdapter adapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, - mLauncher, this); - adapter.setEmptySearchText(res.getString(R.string.loading_apps_message)); - mLayoutManager = adapter.getLayoutManager(context); - mItemDecoration = adapter.getItemDecoration(); - mAdapter = adapter; - mContentMarginStart = adapter.getContentMarginStart(); - } else if (USE_LAYOUT == LIST_LAYOUT) { - mNumAppsPerRow = 1; - AppsListAdapter adapter = new AppsListAdapter(context, mApps, this, mLauncher, this); - adapter.setEmptySearchText(res.getString(R.string.loading_apps_message)); - mLayoutManager = adapter.getLayoutManager(context); - mAdapter = adapter; - } + mNumAppsPerRow = grid.appsViewNumCols; + mApps = new AlphabeticalAppsList(context, mNumAppsPerRow); + mAdapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, mLauncher, this); + mAdapter.setEmptySearchText(res.getString(R.string.loading_apps_message)); + mAdapter.setNumAppsPerRow(mNumAppsPerRow); + mLayoutManager = mAdapter.getLayoutManager(); + mItemDecoration = mAdapter.getItemDecoration(); + mContentMarginStart = mAdapter.getContentMarginStart(); mApps.setAdapter(mAdapter); } @@ -142,12 +136,12 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett } /** - * Hides the search bar + * Hides the header bar */ - public void hideSearchBar() { - mSearchBarView.setVisibility(View.GONE); - updateBackgrounds(); - updatePaddings(); + public void hideHeaderBar() { + mHeaderView.setVisibility(View.GONE); + onUpdateBackgrounds(); + onUpdatePaddings(); } /** @@ -155,6 +149,7 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett */ public void scrollToTop() { mAppsRecyclerView.scrollToPosition(0); + mRecyclerViewScrollY = 0; } /** @@ -175,9 +170,7 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett protected void onFinishInflate() { boolean isRtl = (getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_RTL); - if (USE_LAYOUT == GRID_LAYOUT) { - ((AppsGridAdapter) mAdapter).setRtl(isRtl); - } + mAdapter.setRtl(isRtl); // Work around the search box getting first focus and showing the cursor by // proxying the focus from the content view to the recycler view directly @@ -190,10 +183,29 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett } } }); - mSearchBarView = (EditText) findViewById(R.id.app_search_box); - if (mSearchBarView != null) { - mSearchBarView.addTextChangedListener(this); - mSearchBarView.setOnEditorActionListener(this); + mHeaderView = findViewById(R.id.header); + mHeaderView.setOnClickListener(this); + if (Utilities.isLmpOrAbove() && !DYNAMIC_HEADER_ELEVATION) { + mHeaderView.setElevation(DynamicGrid.pxFromDp(HEADER_ELEVATION_DP, + getContext().getResources().getDisplayMetrics())); + } + mSearchButtonView = mHeaderView.findViewById(R.id.search_button); + mSearchBarContainerView = findViewById(R.id.app_search_container); + mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button); + mDismissSearchButtonView.setOnClickListener(this); + mSearchBarEditView = (AppsContainerSearchEditTextView) findViewById(R.id.app_search_box); + if (mSearchBarEditView != null) { + mSearchBarEditView.addTextChangedListener(this); + mSearchBarEditView.setOnEditorActionListener(this); + if (DISMISS_SEARCH_ON_BACK) { + mSearchBarEditView.setOnBackKeyListener( + new AppsContainerSearchEditTextView.OnBackKeyListener() { + @Override + public void onBackKey() { + hideSearchField(true, true); + } + }); + } } mAppsRecyclerView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view); mAppsRecyclerView.setApps(mApps); @@ -201,39 +213,96 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett mAppsRecyclerView.setLayoutManager(mLayoutManager); mAppsRecyclerView.setAdapter(mAdapter); mAppsRecyclerView.setHasFixedSize(true); + mAppsRecyclerView.setOnScrollListenerProxy(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + // Do nothing + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + mRecyclerViewScrollY += dy; + onRecyclerViewScrolled(); + } + }); if (mItemDecoration != null) { mAppsRecyclerView.addItemDecoration(mItemDecoration); } - updateBackgrounds(); - updatePaddings(); + onUpdateBackgrounds(); + onUpdatePaddings(); } @Override - public void setInsets(Rect insets) { - mInsets.set(insets); - updatePaddings(); + protected void onFixedBoundsUpdated() { + // Update the number of items in the grid + LauncherAppState app = LauncherAppState.getInstance(); + DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); + if (grid.updateAppsViewNumCols(getContext().getResources(), mFixedBounds.width())) { + mNumAppsPerRow = grid.appsViewNumCols; + mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow); + mAdapter.setNumAppsPerRow(mNumAppsPerRow); + mApps.setNumAppsPerRow(mNumAppsPerRow); + } } /** - * Sets the fixed bounds for this Apps view. + * Update the padding of the Apps view and children. To ensure that the RecyclerView has the + * full width to handle touches right to the edge of the screen, we only apply the top and + * bottom padding to the AppsContainerView and then the left/right padding on the RecyclerView + * itself. In particular, the left/right padding is applied to the background of the view, + * and then additionally inset by the start margin. */ - public void setFixedBounds(Context context, Rect fixedBounds) { - if (!fixedBounds.isEmpty() && !fixedBounds.equals(mFixedBounds)) { - // Update the number of items in the grid - LauncherAppState app = LauncherAppState.getInstance(); - DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); - if (grid.updateAppsViewNumCols(context.getResources(), fixedBounds.width())) { - mNumAppsPerRow = grid.appsViewNumCols; - mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow); - if (USE_LAYOUT == GRID_LAYOUT) { - ((AppsGridAdapter) mAdapter).setNumAppsPerRow(mNumAppsPerRow); - } - } + @Override + protected void onUpdatePaddings() { + boolean isRtl = (getResources().getConfiguration().getLayoutDirection() == + LAYOUT_DIRECTION_RTL); + boolean hasSearchBar = (mSearchBarEditView != null) && + (mSearchBarEditView.getVisibility() == View.VISIBLE); - mFixedBounds.set(fixedBounds); + if (mFixedBounds.isEmpty()) { + // If there are no fixed bounds, then use the default padding and insets + setPadding(mInsets.left, mContainerInset + mInsets.top, mInsets.right, + mContainerInset + mInsets.bottom); + } else { + // If there are fixed bounds, then we update the padding to reflect the fixed bounds. + setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right, + mInsets.bottom); + } + + // Update the apps recycler view, inset it by the container inset as well + int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset; + if (isRtl) { + mAppsRecyclerView.setPadding(inset, inset, inset + mContentMarginStart, inset); + } else { + mAppsRecyclerView.setPadding(inset + mContentMarginStart, inset, inset, inset); + } + + // Update the header bar + if (hasSearchBar) { + LinearLayout.LayoutParams lp = + (LinearLayout.LayoutParams) mHeaderView.getLayoutParams(); + lp.leftMargin = lp.rightMargin = inset; } - updateBackgrounds(); - updatePaddings(); + } + + /** + * Update the background of the Apps view and children. + */ + @Override + protected void onUpdateBackgrounds() { + int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset; + boolean hasSearchBar = (mSearchBarEditView != null) && + (mSearchBarEditView.getVisibility() == View.VISIBLE); + + // Update the background of the reveal view and list to be inset with the fixed bound + // insets instead of the default insets + mAppsRecyclerView.setBackground(new InsetDrawable( + getContext().getResources().getDrawable( + hasSearchBar ? R.drawable.apps_list_search_bg : R.drawable.apps_list_bg), + inset, 0, inset, 0)); + getRevealView().setBackground(new InsetDrawable( + getContext().getResources().getDrawable(R.drawable.apps_reveal_bg), + inset, 0, inset, 0)); } @Override @@ -258,6 +327,15 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett } @Override + public void onClick(View v) { + if (v == mHeaderView) { + showSearchField(); + } else if (v == mDismissSearchButtonView) { + hideSearchField(true, true); + } + } + + @Override public boolean onLongClick(View v) { // Return early if this is not initiated from a touch if (!v.isInTouchMode()) return false; @@ -356,24 +434,27 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett mApps.setFilter(null); } else { String formatStr = getResources().getString(R.string.apps_view_no_search_results); - if (USE_LAYOUT == GRID_LAYOUT) { - ((AppsGridAdapter) mAdapter).setEmptySearchText(String.format(formatStr, - s.toString())); - } else { - ((AppsListAdapter) mAdapter).setEmptySearchText(String.format(formatStr, - s.toString())); - } + mAdapter.setEmptySearchText(String.format(formatStr, s.toString())); final String filterText = s.toString().toLowerCase().replaceAll("\\s+", ""); mApps.setFilter(new AlphabeticalAppsList.Filter() { @Override public boolean retainApp(AppInfo info, String sectionName) { String title = info.title.toString(); - return sectionName.toLowerCase().contains(filterText) || - title.toLowerCase().replaceAll("\\s+", "").contains(filterText); + if (sectionName.toLowerCase().contains(filterText)) { + return true; + } + String[] words = title.toLowerCase().split("\\s+"); + for (int i = 0; i < words.length; i++) { + if (words[i].startsWith(filterText)) { + return true; + } + } + return false; } }); } + scrollToTop(); } @Override @@ -389,9 +470,7 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett AlphabeticalAppsList.AdapterItem item = items.get(i); if (!item.isSectionHeader) { mAppsRecyclerView.getChildAt(i).performClick(); - InputMethodManager imm = (InputMethodManager) - getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(getWindowToken(), 0); + getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0); return true; } } @@ -421,10 +500,22 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett @Override public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { - if (mSearchBarView != null) { + if (mSearchBarEditView != null) { if (toWorkspace) { - // Clear the search bar - mSearchBarView.setText(""); + hideSearchField(false, false); + } + } + } + + /** + * Updates the container when the recycler view is scrolled. + */ + private void onRecyclerViewScrolled() { + if (DYNAMIC_HEADER_ELEVATION) { + int elevation = Math.min(mRecyclerViewScrollY, DynamicGrid.pxFromDp(HEADER_ELEVATION_DP, + getContext().getResources().getDisplayMetrics())); + if (Float.compare(mHeaderView.getElevation(), elevation) != 0) { + mHeaderView.setElevation(elevation); } } } @@ -478,61 +569,87 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett } /** - * Update the padding of the Apps view and children. To ensure that the RecyclerView has the - * full width to handle touches right to the edge of the screen, we only apply the top and - * bottom padding to the AppsContainerView and then the left/right padding on the RecyclerView - * itself. In particular, the left/right padding is applied to the background of the view, - * and then additionally inset by the start margin. + * Shows the search field. */ - private void updatePaddings() { - boolean isRtl = (getResources().getConfiguration().getLayoutDirection() == - LAYOUT_DIRECTION_RTL); - boolean hasSearchBar = (mSearchBarView != null) && - (mSearchBarView.getVisibility() == View.VISIBLE); - - if (mFixedBounds.isEmpty()) { - // If there are no fixed bounds, then use the default padding and insets - setPadding(mInsets.left, mContainerInset + mInsets.top, mInsets.right, - mContainerInset + mInsets.bottom); - } else { - // If there are fixed bounds, then we update the padding to reflect the fixed bounds. - setPadding(mFixedBounds.left, mFixedBounds.top + mFixedBoundsContainerInset, - getMeasuredWidth() - mFixedBounds.right, - mInsets.bottom + mFixedBoundsContainerInset); - } + private void showSearchField() { + // Show the search bar and focus the search + final int translationX = DynamicGrid.pxFromDp(SEARCH_TRANSLATION_X_DP, + getContext().getResources().getDisplayMetrics()); + mSearchBarContainerView.setVisibility(View.VISIBLE); + mSearchBarContainerView.setAlpha(0f); + mSearchBarContainerView.setTranslationX(translationX); + mSearchBarContainerView.animate() + .alpha(1f) + .translationX(0) + .setDuration(FADE_IN_DURATION) + .withLayer() + .withEndAction(new Runnable() { + @Override + public void run() { + mSearchBarEditView.requestFocus(); + getInputMethodManager().showSoftInput(mSearchBarEditView, + InputMethodManager.SHOW_IMPLICIT); + } + }); + mSearchButtonView.animate() + .alpha(0f) + .translationX(-translationX) + .setDuration(FADE_OUT_DURATION) + .withLayer(); + } - // Update the apps recycler view - int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset; - if (isRtl) { - mAppsRecyclerView.setPadding(inset, inset, inset + mContentMarginStart, inset); + /** + * Hides the search field. + */ + private void hideSearchField(boolean animated, final boolean returnFocusToRecyclerView) { + final boolean resetTextField = mSearchBarEditView.getText().toString().length() > 0; + final int translationX = DynamicGrid.pxFromDp(SEARCH_TRANSLATION_X_DP, + getContext().getResources().getDisplayMetrics()); + if (animated) { + // Hide the search bar and focus the recycler view + mSearchBarContainerView.animate() + .alpha(0f) + .translationX(0) + .setDuration(FADE_IN_DURATION) + .withLayer() + .withEndAction(new Runnable() { + @Override + public void run() { + mSearchBarContainerView.setVisibility(View.INVISIBLE); + if (resetTextField) { + mSearchBarEditView.setText(""); + } + mApps.setFilter(null); + if (returnFocusToRecyclerView) { + mAppsRecyclerView.requestFocus(); + } + } + }); + mSearchButtonView.setTranslationX(-translationX); + mSearchButtonView.animate() + .alpha(1f) + .translationX(0) + .setDuration(FADE_OUT_DURATION) + .withLayer(); } else { - mAppsRecyclerView.setPadding(inset + mContentMarginStart, inset, inset, inset); - } - - // Update the search bar - if (hasSearchBar) { - LinearLayout.LayoutParams lp = - (LinearLayout.LayoutParams) mSearchBarView.getLayoutParams(); - lp.leftMargin = lp.rightMargin = inset; + mSearchBarContainerView.setVisibility(View.INVISIBLE); + if (resetTextField) { + mSearchBarEditView.setText(""); + } + mApps.setFilter(null); + mSearchButtonView.setAlpha(1f); + mSearchButtonView.setTranslationX(0f); + if (returnFocusToRecyclerView) { + mAppsRecyclerView.requestFocus(); + } } + getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0); } /** - * Update the background of the Apps view and children. + * Returns an input method manager. */ - private void updateBackgrounds() { - int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset; - boolean hasSearchBar = (mSearchBarView != null) && - (mSearchBarView.getVisibility() == View.VISIBLE); - - // Update the background of the reveal view and list to be inset with the fixed bound - // insets instead of the default insets - mAppsRecyclerView.setBackground(new InsetDrawable( - getContext().getResources().getDrawable( - hasSearchBar ? R.drawable.apps_list_search_bg : R.drawable.apps_list_bg), - inset, 0, inset, 0)); - getRevealView().setBackground(new InsetDrawable( - getContext().getResources().getDrawable(R.drawable.apps_reveal_bg), - inset, 0, inset, 0)); + private InputMethodManager getInputMethodManager() { + return (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); } } diff --git a/src/com/android/launcher3/AppsCustomizeCellLayout.java b/src/com/android/launcher3/AppsCustomizeCellLayout.java deleted file mode 100644 index a50fb6821..000000000 --- a/src/com/android/launcher3/AppsCustomizeCellLayout.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2010 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.view.View; - -public class AppsCustomizeCellLayout extends CellLayout implements Page { - - final FocusIndicatorView mFocusHandlerView; - - public AppsCustomizeCellLayout(Context context) { - super(context); - - mFocusHandlerView = new FocusIndicatorView(context); - addView(mFocusHandlerView, 0); - mFocusHandlerView.getLayoutParams().width = FocusIndicatorView.DEFAULT_LAYOUT_SIZE; - mFocusHandlerView.getLayoutParams().height = FocusIndicatorView.DEFAULT_LAYOUT_SIZE; - } - - @Override - public void removeAllViewsOnPage() { - removeAllViews(); - setLayerType(LAYER_TYPE_NONE, null); - } - - @Override - public void removeViewOnPageAt(int index) { - removeViewAt(index); - } - - @Override - public int getPageChildCount() { - return getChildCount(); - } - - @Override - public View getChildOnPageAt(int i) { - return getChildAt(i); - } - - @Override - public int indexOfChildOnPage(View v) { - return indexOfChild(v); - } - - /** - * Clears all the key listeners for the individual icons. - */ - public void resetChildrenOnKeyListeners() { - ShortcutAndWidgetContainer children = getShortcutsAndWidgets(); - int childCount = children.getChildCount(); - for (int j = 0; j < childCount; ++j) { - children.getChildAt(j).setOnKeyListener(null); - } - } -} diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java index 5bc3981df..259740c60 100644 --- a/src/com/android/launcher3/AppsGridAdapter.java +++ b/src/com/android/launcher3/AppsGridAdapter.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.PointF; import android.graphics.Rect; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; @@ -11,9 +12,9 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; - import com.android.launcher3.util.Thunk; +import java.util.HashMap; import java.util.List; @@ -23,6 +24,7 @@ import java.util.List; class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { public static final String TAG = "AppsGridAdapter"; + private static final boolean DEBUG = false; private static final int SECTION_BREAK_VIEW_TYPE = 0; private static final int ICON_VIEW_TYPE = 1; @@ -48,6 +50,12 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { * Helper class to size the grid items. */ public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup { + + public GridSpanSizer() { + super(); + setSpanIndexCacheEnabled(true); + } + @Override public int getSpanSize(int position) { if (mApps.hasNoFilteredResults()) { @@ -57,7 +65,11 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { if (mApps.getAdapterItems().get(position).isSectionHeader) { // Section break spans full width - return mAppsPerRow; + if (AppsContainerView.GRID_HIDE_SECTION_HEADERS) { + return 0; + } else { + return mAppsPerRow; + } } else { return 1; } @@ -69,30 +81,87 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { */ public class GridItemDecoration extends RecyclerView.ItemDecoration { + private static final boolean FADE_OUT_SECTIONS = false; + + private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>(); + private Rect mTmpBounds = new Rect(); + @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + if (mApps.hasFilter()) { + return; + } + List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems(); - for (int i = 0; i < parent.getChildCount(); i++) { + int childCount = parent.getChildCount(); + int lastSectionTop = 0; + int lastSectionHeight = 0; + for (int i = 0; i < childCount; i++) { View child = parent.getChildAt(i); ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child); - if (shouldDrawItemSection(holder, child, items)) { - // Draw at the parent - AlphabeticalAppsList.AdapterItem item = - items.get(holder.getPosition()); - String section = item.sectionName; - mSectionTextPaint.getTextBounds(section, 0, section.length(), - mTmpBounds); - if (mIsRtl) { - int left = parent.getWidth() - mPaddingStart - mStartMargin; - c.drawText(section, left + (mStartMargin - mTmpBounds.width()) / 2, - child.getTop() + (2 * child.getPaddingTop()) + - mTmpBounds.height(), mSectionTextPaint); - } else { - int left = mPaddingStart; - c.drawText(section, left + (mStartMargin - mTmpBounds.width()) / 2, - child.getTop() + (2 * child.getPaddingTop()) + - mTmpBounds.height(), mSectionTextPaint); + if (shouldDrawItemSection(holder, child, i, items)) { + // At this point, we only draw sections for each section break; + int viewTopOffset = (2 * child.getPaddingTop()); + int pos = holder.getPosition(); + AlphabeticalAppsList.AdapterItem item = items.get(pos); + AlphabeticalAppsList.SectionInfo sectionInfo = item.sectionInfo; + + // Draw all the sections for this index + String lastSectionName = item.sectionName; + for (int j = item.sectionAppIndex; j < sectionInfo.numApps; j++, pos++) { + AlphabeticalAppsList.AdapterItem nextItem = items.get(pos); + String sectionName = nextItem.sectionName; + if (nextItem.sectionInfo != sectionInfo) { + break; + } + if (j > item.sectionAppIndex && sectionName.equals(lastSectionName)) { + continue; + } + + // Find the section code points + PointF sectionBounds = getAndCacheSectionBounds(sectionName); + + // Calculate where to draw the section + int sectionBaseline = (int) (viewTopOffset + sectionBounds.y); + int x = mIsRtl ? parent.getWidth() - mPaddingStart - mStartMargin : + mPaddingStart; + x += (int) ((mStartMargin - sectionBounds.x) / 2f); + int y = child.getTop() + sectionBaseline; + + // Determine whether this is the last row with apps in that section, if + // so, then fix the section to the row allowing it to scroll past the + // baseline, otherwise, bound it to the baseline so it's in the viewport + int appIndexInSection = items.get(pos).sectionAppIndex; + int nextRowPos = Math.min(items.size() - 1, + pos + mAppsPerRow - (appIndexInSection % mAppsPerRow)); + AlphabeticalAppsList.AdapterItem nextRowItem = items.get(nextRowPos); + boolean fixedToRow = !sectionName.equals(nextRowItem.sectionName); + if (!fixedToRow) { + y = Math.max(sectionBaseline, y); + } + + // In addition, if it overlaps with the last section that was drawn, then + // offset it so that it does not overlap + if (lastSectionHeight > 0 && y <= (lastSectionTop + lastSectionHeight)) { + y += lastSectionTop - y + lastSectionHeight; + } + + // Draw the section header + if (FADE_OUT_SECTIONS) { + int alpha = 255; + if (fixedToRow) { + alpha = Math.min(255, + (int) (255 * (Math.max(0, y) / (float) sectionBaseline))); + } + mSectionTextPaint.setAlpha(alpha); + } + c.drawText(sectionName, x, y, mSectionTextPaint); + + lastSectionTop = y; + lastSectionHeight = (int) (sectionBounds.y + mSectionHeaderOffset); + lastSectionName = sectionName; } + i += (sectionInfo.numApps - item.sectionAppIndex); } } } @@ -103,7 +172,23 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { // Do nothing } - private boolean shouldDrawItemSection(ViewHolder holder, View child, + /** + * Given a section name, return the bounds of the given section name. + */ + private PointF getAndCacheSectionBounds(String sectionName) { + PointF bounds = mCachedSectionBounds.get(sectionName); + if (bounds == null) { + mSectionTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTmpBounds); + bounds = new PointF(mSectionTextPaint.measureText(sectionName), mTmpBounds.height()); + mCachedSectionBounds.put(sectionName, bounds); + } + return bounds; + } + + /** + * Returns whether to draw the section for the given child. + */ + private boolean shouldDrawItemSection(ViewHolder holder, View child, int childIndex, List<AlphabeticalAppsList.AdapterItem> items) { // Ensure item is not already removed GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams) @@ -124,8 +209,9 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { if (pos <= 0 || pos >= items.size()) { return false; } - // Only draw the first item in the section (the first one after the section header) - return items.get(pos - 1).isSectionHeader && !items.get(pos).isSectionHeader; + // Draw the section header for the first item in each section + return (childIndex == 0) || + (items.get(pos - 1).isSectionHeader && !items.get(pos).isSectionHeader); } } @@ -144,8 +230,8 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { // Section drawing @Thunk int mPaddingStart; @Thunk int mStartMargin; + @Thunk int mSectionHeaderOffset; @Thunk Paint mSectionTextPaint; - @Thunk Rect mTmpBounds = new Rect(); public AppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow, @@ -163,7 +249,10 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { mTouchListener = touchListener; mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; - mStartMargin = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin); + if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS) { + mStartMargin = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin); + mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.apps_grid_section_y_offset); + } mPaddingStart = res.getDimensionPixelSize(R.dimen.apps_container_inset); mSectionTextPaint = new Paint(); mSectionTextPaint.setTextSize(res.getDimensionPixelSize( @@ -197,7 +286,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { /** * Returns the grid layout manager. */ - public GridLayoutManager getLayoutManager(Context context) { + public GridLayoutManager getLayoutManager() { return mGridLayoutMgr; } @@ -205,7 +294,11 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { * Returns the item decoration for the recycler view. */ public RecyclerView.ItemDecoration getItemDecoration() { - return mItemDecoration; + // We don't draw any headers when we are uncomfortably dense + if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS) { + return mItemDecoration; + } + return null; } /** diff --git a/src/com/android/launcher3/AppsListAdapter.java b/src/com/android/launcher3/AppsListAdapter.java deleted file mode 100644 index ffd309261..000000000 --- a/src/com/android/launcher3/AppsListAdapter.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.android.launcher3; - -import android.content.Context; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -/** - * The linear list view adapter for all the apps. - */ -class AppsListAdapter extends RecyclerView.Adapter<AppsListAdapter.ViewHolder> { - - /** - * ViewHolder for each row. - */ - public static class ViewHolder extends RecyclerView.ViewHolder { - public View mContent; - - public ViewHolder(View v) { - super(v); - mContent = v; - } - } - - private static final int SECTION_BREAK_VIEW_TYPE = 0; - private static final int ICON_VIEW_TYPE = 1; - private static final int EMPTY_VIEW_TYPE = 2; - - private LayoutInflater mLayoutInflater; - private AlphabeticalAppsList mApps; - private View.OnTouchListener mTouchListener; - private View.OnClickListener mIconClickListener; - private View.OnLongClickListener mIconLongClickListener; - private String mEmptySearchText; - - public AppsListAdapter(Context context, AlphabeticalAppsList apps, - View.OnTouchListener touchListener, View.OnClickListener iconClickListener, - View.OnLongClickListener iconLongClickListener) { - mApps = apps; - mLayoutInflater = LayoutInflater.from(context); - mTouchListener = touchListener; - mIconClickListener = iconClickListener; - mIconLongClickListener = iconLongClickListener; - } - - public RecyclerView.LayoutManager getLayoutManager(Context context) { - return new LinearLayoutManager(context); - } - - /** - * Sets the text to show when there are no apps. - */ - public void setEmptySearchText(String query) { - mEmptySearchText = query; - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - switch (viewType) { - case EMPTY_VIEW_TYPE: - return new ViewHolder(mLayoutInflater.inflate(R.layout.apps_empty_view, parent, - false)); - case SECTION_BREAK_VIEW_TYPE: - return new ViewHolder(new View(parent.getContext())); - case ICON_VIEW_TYPE: - // Inflate the row and all the icon children necessary - ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.apps_list_row_view, - parent, false); - BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate( - R.layout.apps_list_row_icon_view, row, false); - LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0, - ViewGroup.LayoutParams.WRAP_CONTENT, 1); - lp.gravity = Gravity.CENTER_VERTICAL; - icon.setLayoutParams(lp); - icon.setOnTouchListener(mTouchListener); - icon.setOnClickListener(mIconClickListener); - icon.setOnLongClickListener(mIconLongClickListener); - icon.setFocusable(true); - row.addView(icon); - return new ViewHolder(row); - default: - throw new RuntimeException("Unexpected view type"); - } - } - - @Override - public void onBindViewHolder(ViewHolder holder, int position) { - switch (holder.getItemViewType()) { - case ICON_VIEW_TYPE: - AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position); - ViewGroup content = (ViewGroup) holder.mContent; - String sectionDescription = item.sectionName; - - // Bind the section header - boolean showSectionHeader = true; - if (position > 0) { - AlphabeticalAppsList.AdapterItem prevItem = - mApps.getAdapterItems().get(position - 1); - showSectionHeader = prevItem.isSectionHeader; - } - TextView tv = (TextView) content.findViewById(R.id.section); - if (showSectionHeader) { - tv.setText(sectionDescription); - tv.setVisibility(View.VISIBLE); - } else { - tv.setVisibility(View.INVISIBLE); - } - - // Bind the icon - BubbleTextView icon = (BubbleTextView) content.getChildAt(1); - icon.applyFromApplicationInfo(item.appInfo); - break; - case EMPTY_VIEW_TYPE: - TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text); - emptyViewText.setText(mEmptySearchText); - break; - } - } - - @Override - public int getItemCount() { - if (mApps.hasNoFilteredResults()) { - // For the empty view - return 1; - } - return mApps.getAdapterItems().size(); - } - - @Override - public int getItemViewType(int position) { - if (mApps.hasNoFilteredResults()) { - return EMPTY_VIEW_TYPE; - } else if (mApps.getAdapterItems().get(position).isSectionHeader) { - return SECTION_BREAK_VIEW_TYPE; - } - return ICON_VIEW_TYPE; - } -} diff --git a/src/com/android/launcher3/BaseContainerRecyclerView.java b/src/com/android/launcher3/BaseContainerRecyclerView.java new file mode 100644 index 000000000..5b30e3df6 --- /dev/null +++ b/src/com/android/launcher3/BaseContainerRecyclerView.java @@ -0,0 +1,113 @@ +/* + * 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.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.MotionEvent; +import com.android.launcher3.util.Thunk; + +/** + * A base {@link RecyclerView}, which will NOT intercept a touch sequence unless the scrolling + * velocity is below a predefined threshold. + */ +public class BaseContainerRecyclerView extends RecyclerView + implements RecyclerView.OnItemTouchListener { + + private static final int SCROLL_DELTA_THRESHOLD_DP = 4; + + /** Keeps the last known scrolling delta/velocity along y-axis. */ + @Thunk int mDy = 0; + private float mDeltaThreshold; + private RecyclerView.OnScrollListener mScrollListenerProxy; + + public BaseContainerRecyclerView(Context context) { + this(context, null); + } + + public BaseContainerRecyclerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BaseContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP; + + ScrollListener listener = new ScrollListener(); + setOnScrollListener(listener); + } + + private class ScrollListener extends OnScrollListener { + public ScrollListener() { + // Do nothing + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + mDy = dy; + if (mScrollListenerProxy != null) { + mScrollListenerProxy.onScrolled(recyclerView, dx, dy); + } + } + } + + /** + * Sets an additional scroll listener, only needed for LMR1 version of the support lib. + */ + public void setOnScrollListenerProxy(RecyclerView.OnScrollListener listener) { + mScrollListenerProxy = listener; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + addOnItemTouchListener(this); + } + + @Override + public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) { + if (shouldStopScroll(ev)) { + stopScroll(); + } + return false; + } + + @Override + public void onTouchEvent(RecyclerView rv, MotionEvent ev) { + // Do nothing. + } + + public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { + // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS + } + + /** + * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped. + */ + protected boolean shouldStopScroll(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + if ((Math.abs(mDy) < mDeltaThreshold && + getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) { + // now the touch events are being passed to the {@link WidgetCell} until the + // touch sequence goes over the touch slop. + return true; + } + } + return false; + } +}
\ No newline at end of file diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java new file mode 100644 index 000000000..2a8443221 --- /dev/null +++ b/src/com/android/launcher3/BaseContainerView.java @@ -0,0 +1,100 @@ +/* + * 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.Rect; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +/** + * A base container view, which supports resizing. + */ +public class BaseContainerView extends FrameLayout implements Insettable { + + protected Rect mInsets = new Rect(); + protected Rect mFixedBounds = new Rect(); + protected int mFixedBoundsContainerInset; + + public BaseContainerView(Context context) { + this(context, null); + } + + public BaseContainerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BaseContainerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mFixedBoundsContainerInset = context.getResources().getDimensionPixelSize( + R.dimen.container_fixed_bounds_inset); + } + + @Override + final public void setInsets(Rect insets) { + mInsets.set(insets); + onUpdateBackgrounds(); + onUpdatePaddings(); + } + + /** + * Sets the fixed bounds for this container view. + */ + final public void setFixedBounds(Rect fixedBounds) { + if (!fixedBounds.isEmpty() && !fixedBounds.equals(mFixedBounds)) { + mFixedBounds.set(fixedBounds); + if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) { + mFixedBounds.top = mInsets.top; + mFixedBounds.bottom = getMeasuredHeight(); + } + // To ensure that the child RecyclerView has the full width to handle touches right to + // the edge of the screen, we only apply the top and bottom padding to the bounds + mFixedBounds.inset(0, mFixedBoundsContainerInset); + onFixedBoundsUpdated(); + } + // Post the updates since they can trigger a relayout, and this call can be triggered from + // a layout pass itself. + post(new Runnable() { + @Override + public void run() { + onUpdateBackgrounds(); + onUpdatePaddings(); + } + }); + } + + /** + * Update the UI in response to a change in the fixed bounds. + */ + protected void onFixedBoundsUpdated() { + // Do nothing + } + + /** + * Update the paddings in response to a change in the bounds or insets. + */ + protected void onUpdatePaddings() { + // Do nothing + } + + /** + * Update the backgrounds in response to a change in the bounds or insets. + */ + protected void onUpdateBackgrounds() { + // Do nothing + } +}
\ No newline at end of file diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 65c67025f..72eabf177 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -3051,4 +3051,21 @@ public class CellLayout extends ViewGroup { public boolean findVacantCell(int spanX, int spanY, int[] outXY) { return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied); } + + public boolean isRegionVacant(int x, int y, int spanX, int spanY) { + int x2 = x + spanX - 1; + int y2 = y + spanY - 1; + if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) { + return false; + } + for (int i = x; i <= x2; i++) { + for (int j = y; j <= y2; j++) { + if (mOccupied[i][j]) { + return false; + } + } + } + + return true; + } } diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index deb807501..918517ebd 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -428,6 +428,13 @@ public class DeviceProfile { } public boolean updateAppsViewNumCols(Resources res, int containerWidth) { + if (AppsContainerView.GRID_HIDE_SECTION_HEADERS) { + if (appsViewNumCols != allAppsNumCols) { + appsViewNumCols = allAppsNumCols; + return true; + } + return false; + } int appsViewLeftMarginPx = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin); int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx; diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java index 1d198ba14..211bbfe74 100644 --- a/src/com/android/launcher3/FolderPagedView.java +++ b/src/com/android/launcher3/FolderPagedView.java @@ -78,7 +78,6 @@ public class FolderPagedView extends PagedView { public FolderPagedView(Context context, AttributeSet attrs) { super(context, attrs); LauncherAppState app = LauncherAppState.getInstance(); - setDataIsReady(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); if (ALLOW_FOLDER_SCROLL) { @@ -346,15 +345,6 @@ public class FolderPagedView extends PagedView { } } - @Override - protected void loadAssociatedPages(int page, boolean immediateAndOnly) { } - - @Override - public void syncPages() { } - - @Override - public void syncPageItems(int page, boolean immediate) { } - public int getDesiredWidth() { return getPageCount() > 0 ? getPageAt(0).getDesiredWidth() : 0; } diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index fd4571482..6c2aa397d 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -407,6 +407,20 @@ public class IconCache { } /** + * Updates {@param application} only if a valid entry is found. + */ + public synchronized void updateTitleAndIcon(AppInfo application) { + CacheEntry entry = cacheLocked(application.componentName, null, application.user, + false, application.usingLowResIcon); + if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) { + application.title = entry.title; + application.iconBitmap = entry.icon; + application.contentDescription = entry.contentDescription; + application.usingLowResIcon = entry.isLowResIcon; + } + } + + /** * Returns a high res icon for the given intent and user */ public synchronized Bitmap getIcon(Intent intent, UserHandleCompat user) { @@ -655,7 +669,7 @@ public class IconCache { } private static final class IconDB extends SQLiteOpenHelper { - private final static int DB_VERSION = 3; + private final static int DB_VERSION = 4; private final static String TABLE_NAME = "icons"; private final static String COLUMN_ROWID = "rowid"; diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 4533089e9..564575904 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -135,6 +135,9 @@ public class Launcher extends Activity static final String TAG = "Launcher"; static final boolean LOGD = true; + // Temporary flag + static final boolean DISABLE_ALL_APPS_SEARCH_INTEGRATION = true; + static final boolean PROFILE_STARTUP = false; static final boolean DEBUG_WIDGETS = true; static final boolean DEBUG_STRICT_MODE = false; @@ -525,15 +528,18 @@ public class Launcher extends Activity if (LOGD) { Log.d(TAG, "onAllAppsBoundsChanged(Rect): " + bounds); } - mAppsView.setFixedBounds(Launcher.this, bounds); + mAppsView.setFixedBounds(bounds); + mWidgetsView.setFixedBounds(bounds); } @Override public void dismissAllApps() { - // Dismiss All Apps if we aren't already paused/invisible - if (!mPaused) { - showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true, - null /* onCompleteRunnable */, false /* notifyLauncherCallbacks */); + if (!DISABLE_ALL_APPS_SEARCH_INTEGRATION) { + // Dismiss All Apps if we aren't already paused/invisible + if (!mPaused) { + showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true, + null /* onCompleteRunnable */, false /* notifyLauncherCallbacks */); + } } } }); @@ -1019,7 +1025,7 @@ public class Launcher extends Activity mOnResumeState = State.NONE; // Restore the apps state if we are in all apps - if (mState == State.APPS) { + if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mState == State.APPS) { if (mLauncherCallbacks != null) { mLauncherCallbacks.onAllAppsShown(); } @@ -1453,8 +1459,8 @@ public class Launcher extends Activity // Setup Apps mAppsView = (AppsContainerView) findViewById(R.id.apps_view); - if (mLauncherCallbacks != null && mLauncherCallbacks.overrideAllAppsSearch()) { - mAppsView.hideSearchBar(); + if (isAllAppsSearchOverridden()) { + mAppsView.hideHeaderBar(); } // Setup AppsCustomize @@ -2877,15 +2883,22 @@ public class Launcher extends Activity /** Updates the interaction state. */ public void updateInteraction(Workspace.State fromState, Workspace.State toState) { - // Only update the interacting state if we are transitioning to/from a view without an + // Only update the interacting state if we are transitioning to/from a view with an // overlay - boolean fromStateWithoutOverlay = fromState != Workspace.State.NORMAL && - fromState != Workspace.State.NORMAL_HIDDEN; - boolean toStateWithoutOverlay = toState != Workspace.State.NORMAL && - toState != Workspace.State.NORMAL_HIDDEN; - if (toStateWithoutOverlay) { + boolean fromStateWithOverlay; + boolean toStateWithOverlay; + if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) { + fromStateWithOverlay = fromState != Workspace.State.NORMAL; + toStateWithOverlay = toState != Workspace.State.NORMAL; + } else { + fromStateWithOverlay = fromState != Workspace.State.NORMAL && + fromState != Workspace.State.NORMAL_HIDDEN; + toStateWithOverlay = toState != Workspace.State.NORMAL && + toState != Workspace.State.NORMAL_HIDDEN; + } + if (toStateWithOverlay) { onInteractionBegin(); - } else if (fromStateWithoutOverlay) { + } else if (fromStateWithOverlay) { onInteractionEnd(); } } @@ -3219,8 +3232,7 @@ public class Launcher extends Activity // The hotseat touch handling does not go through Workspace, and we always allow long press // on hotseat items. final boolean inHotseat = isHotseatLayout(v); - boolean allowLongPress = inHotseat || mWorkspace.allowLongPress(); - if (allowLongPress && !mDragController.isDragging()) { + if (!mDragController.isDragging()) { if (itemUnderLongClick == null) { // User long pressed on empty space mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, @@ -3367,7 +3379,7 @@ public class Launcher extends Activity .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); if (notifyLauncherCallbacks) { // Dismiss all apps when the workspace is shown - if (mLauncherCallbacks != null) { + if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) { mLauncherCallbacks.onAllAppsHidden(); } } @@ -3419,7 +3431,7 @@ public class Launcher extends Activity if (toState == State.APPS) { mStateTransitionAnimation.startAnimationToAllApps(animated); - if (mLauncherCallbacks != null) { + if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) { mLauncherCallbacks.onAllAppsShown(); } } else { @@ -3472,7 +3484,7 @@ public class Launcher extends Activity if (successfulDrop) { // We need to trigger all apps hidden to notify search to update itself before the // delayed call to showWorkspace below - if (mLauncherCallbacks != null) { + if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) { mLauncherCallbacks.onAllAppsHidden(); } } @@ -4166,11 +4178,28 @@ public class Launcher extends Activity } /** + * A runnable that we can dequeue and re-enqueue when all applications are bound (to prevent + * multiple calls to bind the same list.) + */ + @Thunk ArrayList<AppInfo> mTmpAppsList; + private Runnable mBindAllApplicationsRunnable = new Runnable() { + public void run() { + bindAllApplications(mTmpAppsList); + mTmpAppsList = null; + } + }; + + /** * Add the icons for all apps. * * Implementation of the method from LauncherModel.Callbacks. */ public void bindAllApplications(final ArrayList<AppInfo> apps) { + if (waitUntilResume(mBindAllApplicationsRunnable, true)) { + mTmpAppsList = apps; + return; + } + if (mAppsView != null) { mAppsView.setApps(apps); } @@ -4437,9 +4466,12 @@ public class Launcher extends Activity /** * Returns whether the launcher callbacks overrides search in all apps. - * @return */ @Thunk boolean isAllAppsSearchOverridden() { + if (DISABLE_ALL_APPS_SEARCH_INTEGRATION) { + return false; + } + if (mLauncherCallbacks != null) { return mLauncherCallbacks.overrideAllAppsSearch(); } diff --git a/src/com/android/launcher3/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/LauncherAccessibilityDelegate.java index 8a9a0508c..3992e6390 100644 --- a/src/com/android/launcher3/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/LauncherAccessibilityDelegate.java @@ -1,6 +1,9 @@ package com.android.launcher3; import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.appwidget.AppWidgetProviderInfo; +import android.content.DialogInterface; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; @@ -28,6 +31,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { private static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace; private static final int MOVE = R.id.action_move; private static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace; + private static final int RESIZE = R.id.action_resize; public enum DragType { ICON, @@ -62,6 +66,8 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { launcher.getText(R.string.action_move))); mActions.put(MOVE_TO_WORKSPACE, new AccessibilityAction(MOVE_TO_WORKSPACE, launcher.getText(R.string.action_move_to_workspace))); + mActions.put(RESIZE, new AccessibilityAction(RESIZE, + launcher.getText(R.string.action_resize))); } @Override @@ -87,6 +93,10 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { if (item.container >= 0) { info.addAction(mActions.get(MOVE_TO_WORKSPACE)); + } else if (item instanceof LauncherAppWidgetInfo) { + if (!getSupportedResizeActions(host, (LauncherAppWidgetInfo) item).isEmpty()) { + info.addAction(mActions.get(RESIZE)); + } } } if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) { info.addAction(mActions.get(ADD_TO_WORKSPACE)); @@ -102,7 +112,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { return super.performAccessibilityAction(host, action, args); } - public boolean performAction(View host, final ItemInfo item, int action) { + public boolean performAction(final View host, final ItemInfo item, int action) { if (action == REMOVE) { if (DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host)) { announceConfirmation(R.string.item_removed); @@ -167,10 +177,97 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { announceConfirmation(R.string.item_moved); } }); + } else if (action == RESIZE) { + final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item; + final ArrayList<Integer> actions = getSupportedResizeActions(host, info); + CharSequence[] labels = new CharSequence[actions.size()]; + for (int i = 0; i < actions.size(); i++) { + labels[i] = mLauncher.getText(actions.get(i)); + } + + new AlertDialog.Builder(mLauncher) + .setTitle(R.string.action_resize) + .setItems(labels, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + performResizeAction(actions.get(which), host, info); + dialog.dismiss(); + } + }) + .show(); } return false; } + private ArrayList<Integer> getSupportedResizeActions(View host, LauncherAppWidgetInfo info) { + AppWidgetProviderInfo providerInfo = ((LauncherAppWidgetHostView) host).getAppWidgetInfo(); + ArrayList<Integer> actions = new ArrayList<>(); + + CellLayout layout = (CellLayout) host.getParent().getParent(); + if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0) { + if (layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY) || + layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY)) { + actions.add(R.string.action_increase_width); + } + + if (info.spanX > info.minSpanX && info.spanX > 1) { + actions.add(R.string.action_decrease_width); + } + } + + if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) { + if (layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1) || + layout.isRegionVacant(info.cellX, info.cellY - 1, info.spanX, 1)) { + actions.add(R.string.action_increase_height); + } + + if (info.spanY > info.minSpanY && info.spanY > 1) { + actions.add(R.string.action_decrease_height); + } + } + return actions; + } + + private void performResizeAction(int action, View host, LauncherAppWidgetInfo info) { + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) host.getLayoutParams(); + CellLayout layout = (CellLayout) host.getParent().getParent(); + layout.markCellsAsUnoccupiedForView(host); + + if (action == R.string.action_increase_width) { + if (((host.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) + && layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY)) + || !layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY)) { + lp.cellX --; + info.cellX --; + } + lp.cellHSpan ++; + info.spanX ++; + } else if (action == R.string.action_decrease_width) { + lp.cellHSpan --; + info.spanX --; + } else if (action == R.string.action_increase_height) { + if (!layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1)) { + lp.cellY --; + info.cellY --; + } + lp.cellVSpan ++; + info.spanY ++; + } else if (action == R.string.action_decrease_height) { + lp.cellVSpan --; + info.spanY --; + } + + layout.markCellsAsOccupiedForView(host); + Rect sizeRange = new Rect(); + AppWidgetResizeFrame.getWidgetSizeRanges(mLauncher, info.spanX, info.spanY, sizeRange); + ((LauncherAppWidgetHostView) host).updateAppWidgetSize(null, + sizeRange.left, sizeRange.top, sizeRange.right, sizeRange.bottom); + host.requestLayout(); + LauncherModel.updateItemInDatabase(mLauncher, info); + announceConfirmation(mLauncher.getString(R.string.widget_resized, info.spanX, info.spanY)); + } + @Thunk void announceConfirmation(int resId) { announceConfirmation(mLauncher.getResources().getString(resId)); } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 95ff6a49b..e81c8c285 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -107,6 +107,7 @@ public class LauncherModel extends BroadcastReceiver @Thunk DeferredHandler mHandler = new DeferredHandler(); @Thunk LoaderTask mLoaderTask; @Thunk boolean mIsLoaderTaskRunning; + @Thunk boolean mHasLoaderCompletedOnce; private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings"; @@ -128,6 +129,12 @@ public class LauncherModel extends BroadcastReceiver // a normal load, we also clear this set of Runnables. static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>(); + /** + * Set of runnables to be called on the background thread after the workspace binding + * is complete. + */ + static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>(); + @Thunk WeakReference<Callbacks> mCallbacks; // < only access in worker thread > @@ -263,6 +270,19 @@ public class LauncherModel extends BroadcastReceiver } } + /** + * Runs the specified runnable after the loader is complete + */ + private void runAfterBindCompletes(Runnable r) { + if (isLoadingWorkspace() || !mHasLoaderCompletedOnce) { + synchronized (mBindCompleteRunnables) { + mBindCompleteRunnables.add(r); + } + } else { + runOnWorkerThread(r); + } + } + boolean canMigrateFromOldLauncherDb(Launcher launcher) { return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ; } @@ -424,7 +444,7 @@ public class LauncherModel extends BroadcastReceiver * Find a position on the screen for the given size or adds a new screen. * @return screenId and the coordinates for the item. */ - @Thunk static Pair<Long, int[]> findSpaceForItem( + @Thunk Pair<Long, int[]> findSpaceForItem( Context context, ArrayList<Long> workspaceScreens, ArrayList<Long> addedWorkspaceScreensFinal, @@ -432,7 +452,7 @@ public class LauncherModel extends BroadcastReceiver LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>(); // Use sBgItemsIdMap as all the items are already loaded. - // TODO: Throw exception is above condition is not met. + assertWorkspaceLoaded(); synchronized (sBgLock) { for (ItemInfo info : sBgItemsIdMap) { if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { @@ -875,12 +895,18 @@ public class LauncherModel extends BroadcastReceiver updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase"); } + private void assertWorkspaceLoaded() { + if (LauncherAppState.isDogfoodBuild() && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) { + throw new RuntimeException("Trying to add shortcut while loader is running"); + } + } + /** * Returns true if the shortcuts already exists on the workspace. This must be called after * the workspace has been loaded. We identify a shortcut by its intent. - * TODO: Throw exception is above condition is not met. */ - @Thunk static boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) { + @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) { + assertWorkspaceLoaded(); final String intentWithPkg, intentWithoutPkg; final String packageName; if (intent.getComponent() != null) { @@ -1390,6 +1416,16 @@ public class LauncherModel extends BroadcastReceiver mHandler.post(r); } } + + // Run all the bind complete runnables after workspace is bound. + if (!mBindCompleteRunnables.isEmpty()) { + synchronized (mBindCompleteRunnables) { + for (final Runnable r : mBindCompleteRunnables) { + runOnWorkerThread(r); + } + mBindCompleteRunnables.clear(); + } + } } public void stopLoader() { @@ -1615,6 +1651,7 @@ public class LauncherModel extends BroadcastReceiver mLoaderTask = null; } mIsLoaderTaskRunning = false; + mHasLoaderCompletedOnce = true; } } @@ -2730,6 +2767,7 @@ public class LauncherModel extends BroadcastReceiver } if (!mAllAppsLoaded) { loadAllApps(); + updateAllAppsIconsCache(); synchronized (LoaderTask.this) { if (mStopped) { return; @@ -2772,6 +2810,8 @@ public class LauncherModel extends BroadcastReceiver } else { mHandler.post(r); } + loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks), + false /* refresh */); } private void loadAllApps() { @@ -2784,9 +2824,6 @@ public class LauncherModel extends BroadcastReceiver return; } - final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); - mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); - final List<UserHandleCompat> profiles = mUserManager.getUserProfiles(); // Clear the list of apps @@ -2794,7 +2831,7 @@ public class LauncherModel extends BroadcastReceiver for (UserHandleCompat user : profiles) { // Query for the set of apps final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; - List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user); + final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user); if (DEBUG_LOADERS) { Log.d(TAG, "getActivityList took " + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user); @@ -2806,42 +2843,6 @@ public class LauncherModel extends BroadcastReceiver return; } - // Update icon cache - HashSet<String> updatedPackages = mIconCache.updateDBIcons(user, apps); - - // If any package icon has changed (app was updated while launcher was dead), - // update the corresponding shortcuts. - if (!updatedPackages.isEmpty()) { - final ArrayList<ShortcutInfo> updates = new ArrayList<ShortcutInfo>(); - synchronized (sBgLock) { - for (ItemInfo info : sBgItemsIdMap) { - if (info instanceof ShortcutInfo && user.equals(info.user) - && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - ShortcutInfo si = (ShortcutInfo) info; - ComponentName cn = si.getTargetComponent(); - if (cn != null && updatedPackages.contains(cn.getPackageName())) { - si.updateIcon(mIconCache); - updates.add(si); - } - } - } - } - - if (!updates.isEmpty()) { - final UserHandleCompat userFinal = user; - mHandler.post(new Runnable() { - - public void run() { - Callbacks cb = getCallback(); - if (cb != null) { - cb.bindShortcutsChanged( - updates, new ArrayList<ShortcutInfo>(), userFinal); - } - } - }); - } - } - // Create the ApplicationInfos for (int i = 0; i < apps.size(); i++) { LauncherActivityInfoCompat app = apps.get(i); @@ -2849,11 +2850,15 @@ public class LauncherModel extends BroadcastReceiver mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache)); } - if (!user.equals(UserHandleCompat.myUserHandle())) { - ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user); - if (heuristic != null) { - heuristic.processUserApps(apps); - } + final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user); + if (heuristic != null) { + runAfterBindCompletes(new Runnable() { + + @Override + public void run() { + heuristic.processUserApps(apps); + } + }); } } // Huh? Shouldn't this be inside the Runnable below? @@ -2868,8 +2873,6 @@ public class LauncherModel extends BroadcastReceiver final Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindAllApplications(added); - loadAndBindWidgetsAndShortcuts(mApp.getContext(), callbacks, - true /* refresh */); if (DEBUG_LOADERS) { Log.d(TAG, "bound " + added.size() + " apps in " + (SystemClock.uptimeMillis() - bindTime) + "ms"); @@ -2882,12 +2885,76 @@ public class LauncherModel extends BroadcastReceiver // Cleanup any data stored for a deleted user. ManagedProfileHeuristic.processAllUsers(profiles, mContext); + loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks), + true /* refresh */); if (DEBUG_LOADERS) { Log.d(TAG, "Icons processed in " + (SystemClock.uptimeMillis() - loadTime) + "ms"); } } + private void updateAllAppsIconsCache() { + final ArrayList<AppInfo> updatedApps = new ArrayList<>(); + + for (UserHandleCompat user : mUserManager.getUserProfiles()) { + // Query for the set of apps + final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user); + // Fail if we don't have any apps + // TODO: Fix this. Only fail for the current user. + if (apps == null || apps.isEmpty()) { + return; + } + + // Update icon cache + HashSet<String> updatedPackages = mIconCache.updateDBIcons(user, apps); + + // If any package icon has changed (app was updated while launcher was dead), + // update the corresponding shortcuts. + if (!updatedPackages.isEmpty()) { + final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>(); + synchronized (sBgLock) { + for (ItemInfo info : sBgItemsIdMap) { + if (info instanceof ShortcutInfo && user.equals(info.user) + && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { + ShortcutInfo si = (ShortcutInfo) info; + ComponentName cn = si.getTargetComponent(); + if (cn != null && updatedPackages.contains(cn.getPackageName())) { + si.updateIcon(mIconCache); + updatedShortcuts.add(si); + } + } + } + mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps); + } + + if (!updatedShortcuts.isEmpty()) { + final UserHandleCompat userFinal = user; + mHandler.post(new Runnable() { + + public void run() { + Callbacks cb = getCallback(); + if (cb != null) { + cb.bindShortcutsChanged(updatedShortcuts, + new ArrayList<ShortcutInfo>(), userFinal); + } + } + }); + } + } + } + if (!updatedApps.isEmpty()) { + mHandler.post(new Runnable() { + + public void run() { + Callbacks cb = getCallback(); + if (cb != null) { + cb.bindAppsUpdated(updatedApps); + } + } + }); + } + } + public void dumpState() { synchronized (sBgLock) { Log.d(TAG, "mLoaderTask.mContext=" + mContext); @@ -2963,6 +3030,10 @@ public class LauncherModel extends BroadcastReceiver } public void run() { + if (!mHasLoaderCompletedOnce) { + // Loader has not yet run. + return; + } final Context context = mApp.getContext(); final String[] packages = mPackages; diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index a4593ecb4..aa8656730 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -22,13 +22,10 @@ import android.animation.AnimatorSet; import android.animation.LayoutTransition; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Matrix; -import android.graphics.PointF; import android.graphics.Rect; import android.os.Bundle; import android.os.Parcel; @@ -47,23 +44,12 @@ import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; -import android.view.animation.AnimationUtils; -import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; -import android.view.animation.LinearInterpolator; import com.android.launcher3.util.Thunk; import java.util.ArrayList; -interface Page { - public int getPageChildCount(); - public View getChildOnPageAt(int i); - public void removeAllViewsOnPage(); - public void removeViewOnPageAt(int i); - public int indexOfChildOnPage(View v); -} - /** * An abstraction of the original Workspace which supports browsing through a * sequential list of "pages" @@ -88,6 +74,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // The page is moved more than halfway, automatically move to the next page on touch up. private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f; + private static final float MAX_SCROLL_PROGRESS = 1.0f; + // The following constants need to be scaled based on density. The scaled versions will be // assigned to the corresponding member variables below. private static final int FLING_THRESHOLD_VELOCITY = 500; @@ -97,7 +85,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // We are disabling touch interaction of the widget region for factory ROM. private static final boolean DISABLE_TOUCH_INTERACTION = false; private static final boolean DISABLE_TOUCH_SIDE_PAGES = true; - private static final boolean DISABLE_FLING_TO_DELETE = true; public static final int INVALID_RESTORE_PAGE = -1001; @@ -170,7 +157,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected int mUnboundedScrollX; protected int[] mTempVisiblePagesRange = new int[2]; protected boolean mForceDrawAllChildrenNextFrame; - private boolean mSpacePagesAutomatically = false; // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise // it is equal to the scaled overscroll position. We use a separate value so as to prevent @@ -183,11 +169,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private PageSwitchListener mPageSwitchListener; - protected ArrayList<Boolean> mDirtyPageContent; - - // If true, syncPages and syncPageItems will be called to refresh pages - protected boolean mContentIsRefreshable = true; - // If true, modify alpha of neighboring pages as user scrolls left/right protected boolean mFadeInAdjacentScreens = false; @@ -198,22 +179,14 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // If true, the subclass should directly update scrollX itself in its computeScroll method // (SmoothPagedView does this) protected boolean mDeferScrollUpdate = false; - protected boolean mDeferLoadAssociatedPagesUntilScrollCompletes = false; protected boolean mIsPageMoving = false; - // All syncs and layout passes are deferred until data is ready. - protected boolean mIsDataReady = false; - - protected boolean mAllowLongPress = true; - private boolean mWasInOverscroll = false; // Page Indicator @Thunk int mPageIndicatorViewId; @Thunk PageIndicator mPageIndicator; - private boolean mAllowPagedViewAnimations = true; - // The viewport whether the pages are to be contained (the actual view may be larger than the // viewport) private Rect mViewport = new Rect(); @@ -221,10 +194,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // Reordering // We use the min scale to determine how much to expand the actually PagedView measured // dimensions such that when we are zoomed out, the view is not clipped - private int REORDERING_DROP_REPOSITION_DURATION = 200; - protected int REORDERING_REORDER_REPOSITION_DURATION = 300; - protected int REORDERING_ZOOM_IN_OUT_DURATION = 250; - private int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80; + private static int REORDERING_DROP_REPOSITION_DURATION = 200; + private static int REORDERING_REORDER_REPOSITION_DURATION = 300; + private static int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80; + private float mMinScale = 1f; private boolean mUseMinScale = false; protected View mDragView; @@ -242,28 +215,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private Runnable mPostReorderingPreZoomInRunnable; // Convenience/caching - private Matrix mTmpInvMatrix = new Matrix(); - private float[] mTmpPoint = new float[2]; - private int[] mTmpIntPoint = new int[2]; - private Rect mTmpRect = new Rect(); - private Rect mAltTmpRect = new Rect(); - - // Fling to delete - @Thunk int FLING_TO_DELETE_FADE_OUT_DURATION = 350; - private float FLING_TO_DELETE_FRICTION = 0.035f; - // The degrees specifies how much deviation from the up vector to still consider a fling "up" - private float FLING_TO_DELETE_MAX_FLING_DEGREES = 65f; - protected int mFlingToDeleteThresholdVelocity = -1400; - // Drag to delete - @Thunk boolean mDeferringForDelete = false; - @Thunk int DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250; - private int DRAG_TO_DELETE_FADE_OUT_DURATION = 350; - - // Drop to delete - private View mDeleteDropTarget; - - // Bouncer - private boolean mTopAlignPageWhenShrinkingForBouncer = false; + private static final Matrix sTmpInvMatrix = new Matrix(); + private static final float[] sTmpPoint = new float[2]; + private static final int[] sTmpIntPoint = new int[2]; + private static final Rect sTmpRect = new Rect(); protected final Rect mInsets = new Rect(); @@ -300,8 +255,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc * Initializes various states for this workspace. */ protected void init() { - mDirtyPageContent = new ArrayList<Boolean>(); - mDirtyPageContent.ensureCapacity(32); mScroller = new LauncherScroller(getContext()); setDefaultInterpolator(new ScrollInterpolator()); mCurrentPage = 0; @@ -313,10 +266,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mDensity = getResources().getDisplayMetrics().density; - // Scale the fling-to-delete threshold by the density - mFlingToDeleteThresholdVelocity = - (int) (mFlingToDeleteThresholdVelocity * mDensity); - mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity); mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity); mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity); @@ -336,7 +285,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc ViewGroup grandParent = (ViewGroup) parent.getParent(); if (mPageIndicator == null && mPageIndicatorViewId > -1) { mPageIndicator = (PageIndicator) grandParent.findViewById(mPageIndicatorViewId); - mPageIndicator.removeAllMarkers(mAllowPagedViewAnimations); + mPageIndicator.removeAllMarkers(true); ArrayList<PageIndicator.PageMarkerResources> markers = new ArrayList<PageIndicator.PageMarkerResources>(); @@ -344,7 +293,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc markers.add(getPageIndicatorMarker(i)); } - mPageIndicator.addMarkers(markers, mAllowPagedViewAnimations); + mPageIndicator.addMarkers(markers, true); OnClickListener listener = getPageIndicatorClickListener(); if (listener != null) { @@ -362,33 +311,31 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc return null; } + @Override protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); // Unhook the page indicator mPageIndicator = null; } - void setDeleteDropTarget(View v) { - mDeleteDropTarget = v; - } - // Convenience methods to map points from self to parent and vice versa - float[] mapPointFromViewToParent(View v, float x, float y) { - mTmpPoint[0] = x; - mTmpPoint[1] = y; - v.getMatrix().mapPoints(mTmpPoint); - mTmpPoint[0] += v.getLeft(); - mTmpPoint[1] += v.getTop(); - return mTmpPoint; - } - float[] mapPointFromParentToView(View v, float x, float y) { - mTmpPoint[0] = x - v.getLeft(); - mTmpPoint[1] = y - v.getTop(); - v.getMatrix().invert(mTmpInvMatrix); - mTmpInvMatrix.mapPoints(mTmpPoint); - return mTmpPoint; - } - - void updateDragViewTranslationDuringDrag() { + private float[] mapPointFromViewToParent(View v, float x, float y) { + sTmpPoint[0] = x; + sTmpPoint[1] = y; + v.getMatrix().mapPoints(sTmpPoint); + sTmpPoint[0] += v.getLeft(); + sTmpPoint[1] += v.getTop(); + return sTmpPoint; + } + private float[] mapPointFromParentToView(View v, float x, float y) { + sTmpPoint[0] = x - v.getLeft(); + sTmpPoint[1] = y - v.getTop(); + v.getMatrix().invert(sTmpInvMatrix); + sTmpInvMatrix.mapPoints(sTmpPoint); + return sTmpPoint; + } + + private void updateDragViewTranslationDuringDrag() { if (mDragView != null) { float x = (mLastMotionX - mDownMotionX) + (getScrollX() - mDownScrollX) + (mDragViewBaselineLeft - mDragView.getLeft()); @@ -463,18 +410,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } /** - * Called by subclasses to mark that data is ready, and that we can begin loading and laying - * out pages. - */ - protected void setDataIsReady() { - mIsDataReady = true; - } - - protected boolean isDataReady() { - return mIsDataReady; - } - - /** * Returns the index of the currently displayed page. */ int getCurrentPage() { @@ -516,17 +451,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc forceFinishScroller(); } - /** - * Called during AllApps/Home transitions to avoid unnecessary work. When that other animation - * {@link #updateCurrentPageScroll()} should be called, to correctly set the final state and - * re-enable scrolling. - */ - void stopScrolling() { - mCurrentPage = getNextPage(); - notifyPageSwitchListener(); - forceFinishScroller(); - } - private void abortScrollerAnimation(boolean resetNextPage) { mScroller.abortAnimation(); // We need to clean up the next page here to avoid computeScrollHelper from @@ -559,7 +483,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc /** * Sets the current page. */ - void setCurrentPage(int currentPage) { + public void setCurrentPage(int currentPage) { if (!mScroller.isFinished()) { abortScrollerAnimation(true); } @@ -750,12 +674,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mNextPage = INVALID_PAGE; notifyPageSwitchListener(); - // Load the associated pages if necessary - if (mDeferLoadAssociatedPagesUntilScrollCompletes) { - loadAssociatedPages(mCurrentPage); - mDeferLoadAssociatedPagesUntilScrollCompletes = false; - } - // We don't want to trigger a page end moving unless the page has settled // and the user has stopped scrolling if (mTouchState == TOUCH_STATE_REST) { @@ -779,10 +697,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc computeScrollHelper(); } - protected boolean shouldSetTopAlignedPivotForWidget(int childIndex) { - return mTopAlignPageWhenShrinkingForBouncer; - } - public static class LayoutParams extends ViewGroup.LayoutParams { public boolean isFullScreenPage = false; @@ -814,7 +728,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (!mIsDataReady || getChildCount() == 0) { + if (getChildCount() == 0) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); return; } @@ -918,27 +832,12 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } - if (mSpacePagesAutomatically) { - int spacing = (getViewportWidth() - mInsets.left - mInsets.right - - referenceChildWidth) / 2; - if (spacing >= 0) { - setPageSpacing(spacing); - } - mSpacePagesAutomatically = false; - } setMeasuredDimension(scaledWidthSize, scaledHeightSize); } - /** - * This method should be called once before first layout / measure pass. - */ - protected void setSinglePageInViewport() { - mSpacePagesAutomatically = true; - } - @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (!mIsDataReady || getChildCount() == 0) { + if (getChildCount() == 0) { return; } @@ -1040,8 +939,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc updateMaxScrollX(); } - if (mScroller.isFinished() && mChildCountOnLastLayout != childCount && - !mDeferringForDelete) { + if (mScroller.isFinished() && mChildCountOnLastLayout != childCount) { if (mRestorePage != INVALID_RESTORE_PAGE) { setCurrentPage(mRestorePage); mRestorePage = INVALID_RESTORE_PAGE; @@ -1087,14 +985,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } } - protected void enablePagedViewAnimations() { - mAllowPagedViewAnimations = true; - - } - protected void disablePagedViewAnimations() { - mAllowPagedViewAnimations = false; - } - @Override public void onChildViewAdded(View parent, View child) { // Update the page indicator, we don't update the page indicator as we @@ -1103,7 +993,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc int pageIndex = indexOfChild(child); mPageIndicator.addMarker(pageIndex, getPageIndicatorMarker(pageIndex), - mAllowPagedViewAnimations); + true); } // This ensures that when children are added, they get the correct transforms / alphas @@ -1124,7 +1014,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // Update the page indicator, we don't update the page indicator as we // add/remove pages if (mPageIndicator != null && !isReordering(false)) { - mPageIndicator.removeMarker(index, mAllowPagedViewAnimations); + mPageIndicator.removeMarker(index, true); } } @@ -1154,7 +1044,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // Update the page indicator, we don't update the page indicator as we // add/remove pages if (mPageIndicator != null) { - mPageIndicator.removeAllMarkers(mAllowPagedViewAnimations); + mPageIndicator.removeAllMarkers(true); } super.removeAllViewsInLayout(); @@ -1175,7 +1065,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected void getVisiblePages(int[] range) { final int pageCount = getChildCount(); - mTmpIntPoint[0] = mTmpIntPoint[1] = 0; + sTmpIntPoint[0] = sTmpIntPoint[1] = 0; range[0] = -1; range[1] = -1; @@ -1188,9 +1078,9 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc for (int i = 0; i < count; i++) { View currPage = getPageAt(i); - mTmpIntPoint[0] = 0; - Utilities.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false); - if (mTmpIntPoint[0] > viewportWidth) { + sTmpIntPoint[0] = 0; + Utilities.getDescendantCoordRelativeToParent(currPage, this, sTmpIntPoint, false); + if (sTmpIntPoint[0] > viewportWidth) { if (range[0] == -1) { continue; } else { @@ -1198,9 +1088,9 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } } - mTmpIntPoint[0] = currPage.getMeasuredWidth(); - Utilities.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false); - if (mTmpIntPoint[0] < 0) { + sTmpIntPoint[0] = currPage.getMeasuredWidth(); + Utilities.getDescendantCoordRelativeToParent(currPage, this, sTmpIntPoint, false); + if (sTmpIntPoint[0] < 0) { if (range[0] == -1) { continue; } else { @@ -1397,9 +1287,9 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc /** Returns whether x and y originated within the buffered viewport */ private boolean isTouchPointInViewportWithBuffer(int x, int y) { - mTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top, + sTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top, mViewport.right + mViewport.width() / 2, mViewport.bottom); - return mTmpRect.contains(x, y); + return sTmpRect.contains(x, y); } @Override @@ -1559,32 +1449,16 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } } - protected float getMaxScrollProgress() { - return 1.0f; - } - protected void cancelCurrentPageLongPress() { - if (mAllowLongPress) { - //mAllowLongPress = false; - // Try canceling the long press. It could also have been scheduled - // by a distant descendant, so use the mAllowLongPress flag to block - // everything - final View currentPage = getPageAt(mCurrentPage); - if (currentPage != null) { - currentPage.cancelLongPress(); - } + // Try canceling the long press. It could also have been scheduled + // by a distant descendant, so use the mAllowLongPress flag to block + // everything + final View currentPage = getPageAt(mCurrentPage); + if (currentPage != null) { + currentPage.cancelLongPress(); } } - protected float getBoundedScrollProgress(int screenCenter, View v, int page) { - final int halfScreenSize = getViewportWidth() / 2; - - screenCenter = Math.min(getScrollX() + halfScreenSize, screenCenter); - screenCenter = Math.max(halfScreenSize, screenCenter); - - return getScrollProgress(screenCenter, v, page); - } - protected float getScrollProgress(int screenCenter, View v, int page) { final int halfScreenSize = getViewportWidth() / 2; @@ -1605,8 +1479,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } float scrollProgress = delta / (totalDistance * 1.0f); - scrollProgress = Math.min(scrollProgress, getMaxScrollProgress()); - scrollProgress = Math.max(scrollProgress, - getMaxScrollProgress()); + scrollProgress = Math.min(scrollProgress, MAX_SCROLL_PROGRESS); + scrollProgress = Math.max(scrollProgress, - MAX_SCROLL_PROGRESS); return scrollProgress; } @@ -1735,7 +1609,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mAllowOverScroll = enable; } - int getNearestHoverOverPageIndex() { + private int getNearestHoverOverPageIndex() { if (mDragView != null) { int dragX = (int) (mDragView.getLeft() + (mDragView.getMeasuredWidth() / 2) + mDragView.getTranslationX()); @@ -1842,19 +1716,13 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // Find the closest page to the touch point final int dragViewIndex = indexOfChild(mDragView); - // Change the drag view if we are hovering over the drop target - boolean isHoveringOverDelete = isHoveringOverDeleteDropTarget( - (int) mParentDownMotionX, (int) mParentDownMotionY); - setPageHoveringOverDeleteDropTarget(dragViewIndex, isHoveringOverDelete); - if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX); if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY); if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX); if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY); final int pageUnderPointIndex = getNearestHoverOverPageIndex(); - if (pageUnderPointIndex > -1 && pageUnderPointIndex != indexOfChild(mDragView) && - !isHoveringOverDelete) { + if (pageUnderPointIndex > -1 && pageUnderPointIndex != indexOfChild(mDragView)) { mTempVisiblePagesRange[0] = 0; mTempVisiblePagesRange[1] = getPageCount() - 1; getFreeScrollPageRange(mTempVisiblePagesRange); @@ -1901,9 +1769,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } removeView(mDragView); - onRemoveView(mDragView, false); addView(mDragView, pageUnderPointIndex); - onAddView(mDragView, pageUnderPointIndex); mSidePageHoverIndex = -1; if (mPageIndicator != null) { mPageIndicator.setActiveMarker(getNextPage()); @@ -2014,19 +1880,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mParentDownMotionX = pt[0]; mParentDownMotionY = pt[1]; updateDragViewTranslationDuringDrag(); - boolean handledFling = false; - if (!DISABLE_FLING_TO_DELETE) { - // Check the velocity and see if we are flinging-to-delete - PointF flingToDeleteVector = isFlingingToDelete(); - if (flingToDeleteVector != null) { - onFlingToDelete(flingToDeleteVector); - handledFling = true; - } - } - if (!handledFling && isHoveringOverDeleteDropTarget((int) mParentDownMotionX, - (int) mParentDownMotionY)) { - onDropToDelete(); - } } else { if (!mCancelTap) { onUnhandledTap(ev); @@ -2055,11 +1908,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc return true; } - public void onFlingToDelete(View v) {} - public void onRemoveView(View v, boolean deletePermanently) {} - public void onRemoveViewAnimationCompleted() {} - public void onAddView(View v, int index) {} - private void resetTouchState() { releaseVelocityTracker(); endReordering(); @@ -2155,22 +2003,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } } - protected int getChildWidth(int index) { - return getPageAt(index).getMeasuredWidth(); - } - - int getPageNearestToPoint(float x) { - int index = 0; - for (int i = 0; i < getChildCount(); ++i) { - if (x < getChildAt(i).getRight() - getScrollX()) { - return index; - } else { - index++; - } - } - return Math.min(index, getChildCount() - 1); - } - int getPageNearestToCenterOfScreen() { int minDistanceFromScreenCenter = Integer.MAX_VALUE; int minDistanceFromScreenCenterIndex = -1; @@ -2330,9 +2162,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc computeScroll(); } - // Defer loading associated pages until the scroll settles - mDeferLoadAssociatedPagesUntilScrollCompletes = true; - mForceScreenScrolled = true; invalidate(); } @@ -2359,27 +2188,12 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc return result; } - /** - * @return True is long presses are still allowed for the current touch - */ - public boolean allowLongPress() { - return mAllowLongPress; - } - @Override public boolean performLongClick() { mCancelTap = true; return super.performLongClick(); } - /** - * Set true to allow long-press events to be triggered, usually checked by - * {@link Launcher} to accept or block dpad-initiated long-presses. - */ - public void setAllowLongPress(boolean allowLongPress) { - mAllowLongPress = allowLongPress; - } - public static class SavedState extends BaseSavedState { int currentPage = -1; @@ -2410,111 +2224,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc }; } - protected void loadAssociatedPages(int page) { - loadAssociatedPages(page, false); - } - protected void loadAssociatedPages(int page, boolean immediateAndOnly) { - if (mContentIsRefreshable) { - final int count = getChildCount(); - if (page < count) { - int lowerPageBound = getAssociatedLowerPageBound(page); - int upperPageBound = getAssociatedUpperPageBound(page); - if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/" - + upperPageBound); - // First, clear any pages that should no longer be loaded - for (int i = 0; i < count; ++i) { - Page layout = (Page) getPageAt(i); - if ((i < lowerPageBound) || (i > upperPageBound)) { - if (layout.getPageChildCount() > 0) { - layout.removeAllViewsOnPage(); - } - mDirtyPageContent.set(i, true); - } - } - // Next, load any new pages - for (int i = 0; i < count; ++i) { - if ((i != page) && immediateAndOnly) { - continue; - } - if (lowerPageBound <= i && i <= upperPageBound) { - if (mDirtyPageContent.get(i)) { - syncPageItems(i, (i == page) && immediateAndOnly); - mDirtyPageContent.set(i, false); - } - } - } - } - } - } - - protected int getAssociatedLowerPageBound(int page) { - return Math.max(0, page - 1); - } - protected int getAssociatedUpperPageBound(int page) { - final int count = getChildCount(); - return Math.min(page + 1, count - 1); - } - - /** - * This method is called ONLY to synchronize the number of pages that the paged view has. - * To actually fill the pages with information, implement syncPageItems() below. It is - * guaranteed that syncPageItems() will be called for a particular page before it is shown, - * and therefore, individual page items do not need to be updated in this method. - */ - public abstract void syncPages(); - - /** - * This method is called to synchronize the items that are on a particular page. If views on - * the page can be reused, then they should be updated within this method. - */ - public abstract void syncPageItems(int page, boolean immediate); - - protected void invalidatePageData() { - invalidatePageData(-1, false); - } - protected void invalidatePageData(int currentPage) { - invalidatePageData(currentPage, false); - } - protected void invalidatePageData(int currentPage, boolean immediateAndOnly) { - if (!mIsDataReady) { - return; - } - - if (mContentIsRefreshable) { - // Force all scrolling-related behavior to end - forceFinishScroller(); - - // Update all the pages - syncPages(); - - // We must force a measure after we've loaded the pages to update the content width and - // to determine the full scroll width - measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); - - // Set a new page as the current page if necessary - if (currentPage > -1) { - setCurrentPage(Math.min(getPageCount() - 1, currentPage)); - } - - // Mark each of the pages as dirty - final int count = getChildCount(); - mDirtyPageContent.clear(); - for (int i = 0; i < count; ++i) { - mDirtyPageContent.add(true); - } - - // Load any pages that are necessary for the current window of views - loadAssociatedPages(mCurrentPage, immediateAndOnly); - requestLayout(); - } - if (isPageMoving()) { - // If the page is moving, then snap it to the final position to ensure we don't get - // stuck between pages - snapToDestination(); - } - } - // Animate the drag view back to the original position void animateDragViewToOriginalPosition() { if (mDragView != null) { @@ -2535,7 +2244,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } } - protected void onStartReordering() { + public void onStartReordering() { // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.) mTouchState = TOUCH_STATE_REORDERING; mIsReordering = true; @@ -2555,7 +2264,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } } - protected void onEndReordering() { + public void onEndReordering() { mIsReordering = false; } @@ -2605,279 +2314,23 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc onEndReordering(); } }; - if (!mDeferringForDelete) { - mPostReorderingPreZoomInRunnable = new Runnable() { - public void run() { - onCompleteRunnable.run(); - enableFreeScroll(); - }; - }; - - mPostReorderingPreZoomInRemainingAnimationCount = - NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT; - // Snap to the current page - snapToPage(indexOfChild(mDragView), 0); - // Animate the drag view back to the front position - animateDragViewToOriginalPosition(); - } else { - // Handled in post-delete-animation-callbacks - } - } - - /* - * Flinging to delete - IN PROGRESS - */ - private PointF isFlingingToDelete() { - ViewConfiguration config = ViewConfiguration.get(getContext()); - mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity()); - - if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) { - // Do a quick dot product test to ensure that we are flinging upwards - PointF vel = new PointF(mVelocityTracker.getXVelocity(), - mVelocityTracker.getYVelocity()); - PointF upVec = new PointF(0f, -1f); - float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) / - (vel.length() * upVec.length())); - if (theta <= Math.toRadians(FLING_TO_DELETE_MAX_FLING_DEGREES)) { - return vel; - } - } - return null; - } - - /** - * Creates an animation from the current drag view along its current velocity vector. - * For this animation, the alpha runs for a fixed duration and we update the position - * progressively. - */ - private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener { - private View mDragView; - private PointF mVelocity; - private Rect mFrom; - private long mPrevTime; - private float mFriction; - - private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f); - - public FlingAlongVectorAnimatorUpdateListener(View dragView, PointF vel, Rect from, - long startTime, float friction) { - mDragView = dragView; - mVelocity = vel; - mFrom = from; - mPrevTime = startTime; - mFriction = 1f - (mDragView.getResources().getDisplayMetrics().density * friction); - } - - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = ((Float) animation.getAnimatedValue()).floatValue(); - long curTime = AnimationUtils.currentAnimationTimeMillis(); - - mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f); - mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f); - mDragView.setTranslationX(mFrom.left); - mDragView.setTranslationY(mFrom.top); - mDragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t)); - - mVelocity.x *= mFriction; - mVelocity.y *= mFriction; - mPrevTime = curTime; - } - }; - - private static final int ANIM_TAG_KEY = 100; - - private Runnable createPostDeleteAnimationRunnable(final View dragView) { - return new Runnable() { - @Override + mPostReorderingPreZoomInRunnable = new Runnable() { public void run() { - int dragViewIndex = indexOfChild(dragView); - - // For each of the pages around the drag view, animate them from the previous - // position to the new position in the layout (as a result of the drag view moving - // in the layout) - // NOTE: We can make an assumption here because we have side-bound pages that we - // will always have pages to animate in from the left - getFreeScrollPageRange(mTempVisiblePagesRange); - boolean isLastWidgetPage = (mTempVisiblePagesRange[0] == mTempVisiblePagesRange[1]); - boolean slideFromLeft = (isLastWidgetPage || - dragViewIndex > mTempVisiblePagesRange[0]); - - // Setup the scroll to the correct page before we swap the views - if (slideFromLeft) { - snapToPageImmediately(dragViewIndex - 1); - } - - int firstIndex = (isLastWidgetPage ? 0 : mTempVisiblePagesRange[0]); - int lastIndex = Math.min(mTempVisiblePagesRange[1], getPageCount() - 1); - int lowerIndex = (slideFromLeft ? firstIndex : dragViewIndex + 1 ); - int upperIndex = (slideFromLeft ? dragViewIndex - 1 : lastIndex); - ArrayList<Animator> animations = new ArrayList<Animator>(); - for (int i = lowerIndex; i <= upperIndex; ++i) { - View v = getChildAt(i); - // dragViewIndex < pageUnderPointIndex, so after we remove the - // drag view all subsequent views to pageUnderPointIndex will - // shift down. - int oldX = 0; - int newX = 0; - if (slideFromLeft) { - if (i == 0) { - // Simulate the page being offscreen with the page spacing - oldX = getViewportOffsetX() + getChildOffset(i) - getChildWidth(i) - - mPageSpacing; - } else { - oldX = getViewportOffsetX() + getChildOffset(i - 1); - } - newX = getViewportOffsetX() + getChildOffset(i); - } else { - oldX = getChildOffset(i) - getChildOffset(i - 1); - newX = 0; - } - - // Animate the view translation from its old position to its new - // position - AnimatorSet anim = (AnimatorSet) v.getTag(); - if (anim != null) { - anim.cancel(); - } - - // Note: Hacky, but we want to skip any optimizations to not draw completely - // hidden views - v.setAlpha(Math.max(v.getAlpha(), 0.01f)); - v.setTranslationX(oldX - newX); - anim = new AnimatorSet(); - anim.playTogether( - ObjectAnimator.ofFloat(v, "translationX", 0f), - ObjectAnimator.ofFloat(v, "alpha", 1f)); - animations.add(anim); - v.setTag(ANIM_TAG_KEY, anim); - } - - AnimatorSet slideAnimations = new AnimatorSet(); - slideAnimations.playTogether(animations); - slideAnimations.setDuration(DELETE_SLIDE_IN_SIDE_PAGE_DURATION); - slideAnimations.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mDeferringForDelete = false; - onEndReordering(); - onRemoveViewAnimationCompleted(); - } - }); - slideAnimations.start(); - - removeView(dragView); - onRemoveView(dragView, true); - } - }; - } - - public void onFlingToDelete(PointF vel) { - final long startTime = AnimationUtils.currentAnimationTimeMillis(); - - // NOTE: Because it takes time for the first frame of animation to actually be - // called and we expect the animation to be a continuation of the fling, we have - // to account for the time that has elapsed since the fling finished. And since - // we don't have a startDelay, we will always get call to update when we call - // start() (which we want to ignore). - final TimeInterpolator tInterpolator = new TimeInterpolator() { - private int mCount = -1; - private long mStartTime; - private float mOffset; - /* Anonymous inner class ctor */ { - mStartTime = startTime; - } - - @Override - public float getInterpolation(float t) { - if (mCount < 0) { - mCount++; - } else if (mCount == 0) { - mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() - - mStartTime) / FLING_TO_DELETE_FADE_OUT_DURATION); - mCount++; - } - return Math.min(1f, mOffset + t); - } + onCompleteRunnable.run(); + enableFreeScroll(); + }; }; - final Rect from = new Rect(); - final View dragView = mDragView; - from.left = (int) dragView.getTranslationX(); - from.top = (int) dragView.getTranslationY(); - AnimatorUpdateListener updateCb = new FlingAlongVectorAnimatorUpdateListener(dragView, vel, - from, startTime, FLING_TO_DELETE_FRICTION); - - final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView); - - // Create and start the animation - ValueAnimator mDropAnim = new ValueAnimator(); - mDropAnim.setInterpolator(tInterpolator); - mDropAnim.setDuration(FLING_TO_DELETE_FADE_OUT_DURATION); - mDropAnim.setFloatValues(0f, 1f); - mDropAnim.addUpdateListener(updateCb); - mDropAnim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - onAnimationEndRunnable.run(); - } - }); - mDropAnim.start(); - mDeferringForDelete = true; - } - - /* Drag to delete */ - private boolean isHoveringOverDeleteDropTarget(int x, int y) { - if (mDeleteDropTarget != null) { - mAltTmpRect.set(0, 0, 0, 0); - View parent = (View) mDeleteDropTarget.getParent(); - if (parent != null) { - parent.getGlobalVisibleRect(mAltTmpRect); - } - mDeleteDropTarget.getGlobalVisibleRect(mTmpRect); - mTmpRect.offset(-mAltTmpRect.left, -mAltTmpRect.top); - return mTmpRect.contains(x, y); - } - return false; + mPostReorderingPreZoomInRemainingAnimationCount = + NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT; + // Snap to the current page + snapToPage(indexOfChild(mDragView), 0); + // Animate the drag view back to the front position + animateDragViewToOriginalPosition(); } - protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) {} - - private void onDropToDelete() { - final View dragView = mDragView; - - final float toScale = 0f; - final float toAlpha = 0f; - - // Create and start the complex animation - ArrayList<Animator> animations = new ArrayList<Animator>(); - AnimatorSet motionAnim = new AnimatorSet(); - motionAnim.setInterpolator(new DecelerateInterpolator(2)); - motionAnim.playTogether( - ObjectAnimator.ofFloat(dragView, "scaleX", toScale), - ObjectAnimator.ofFloat(dragView, "scaleY", toScale)); - animations.add(motionAnim); - - AnimatorSet alphaAnim = new AnimatorSet(); - alphaAnim.setInterpolator(new LinearInterpolator()); - alphaAnim.playTogether( - ObjectAnimator.ofFloat(dragView, "alpha", toAlpha)); - animations.add(alphaAnim); - - final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView); - - AnimatorSet anim = new AnimatorSet(); - anim.playTogether(animations); - anim.setDuration(DRAG_TO_DELETE_FADE_OUT_DURATION); - anim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - onAnimationEndRunnable.run(); - } - }); - anim.start(); - - mDeferringForDelete = true; - } + private static final int ANIM_TAG_KEY = 100; /* Accessibility */ @Override diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index e7a41e09b..5c2121ab8 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -63,6 +63,7 @@ import com.android.launcher3.Launcher.LauncherOverlay; import com.android.launcher3.LauncherAccessibilityDelegate.AccessibilityDragSource; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.UninstallDropTarget.UninstallSource; +import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.Thunk; @@ -94,7 +95,6 @@ public class Workspace extends SmoothPagedView protected static final int FADE_EMPTY_SCREEN_DURATION = 150; private static final int ADJACENT_SCREEN_DROP_DURATION = 300; - private static final int FLING_THRESHOLD_VELOCITY = 500; static final boolean MAP_NO_RECURSE = false; static final boolean MAP_RECURSE = true; @@ -277,6 +277,8 @@ public class Workspace extends SmoothPagedView // Handles workspace state transitions private WorkspaceStateTransitionAnimation mStateTransitionAnimation; + private AccessibilityDelegate mPagesAccessibilityDelegate; + private final Runnable mBindPages = new Runnable() { @Override public void run() { @@ -303,13 +305,11 @@ public class Workspace extends SmoothPagedView */ public Workspace(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mContentIsRefreshable = false; mOutlineHelper = HolographicOutlineHelper.obtain(context); mDragEnforcer = new DropTarget.DragEnforcer(context); // With workspace, data is available straight from the get-go - setDataIsReady(); mLauncher = (Launcher) context; mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this); @@ -445,7 +445,6 @@ public class Workspace extends SmoothPagedView display.getSize(mDisplaySize); mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx); - mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity); // Set the wallpaper dimensions when Launcher starts up setWallpaperDimension(); @@ -2000,14 +1999,14 @@ public class Workspace extends SmoothPagedView range[1] = Math.max(0, end); } - protected void onStartReordering() { + public void onStartReordering() { super.onStartReordering(); showOutlines(); // Reordering handles its own animations, disable the automatic ones. disableLayoutTransitions(); } - protected void onEndReordering() { + public void onEndReordering() { super.onEndReordering(); if (mLauncher.isWorkspaceLoading()) { @@ -2068,11 +2067,45 @@ public class Workspace extends SmoothPagedView return mState; } - private void updateAccessibilityFlags() { - int accessible = mState == State.NORMAL ? - IMPORTANT_FOR_ACCESSIBILITY_NO : - IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; - setImportantForAccessibility(accessible); + public void updateAccessibilityFlags() { + if (Utilities.isLmpOrAbove()) { + int total = getPageCount(); + for (int i = numCustomPages(); i < total; i++) { + updateAccessibilityFlags((CellLayout) getPageAt(i), i); + } + if (mState == State.NORMAL) { + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + } else { + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + } + } else { + int accessible = mState == State.NORMAL ? + IMPORTANT_FOR_ACCESSIBILITY_NO : + IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; + setImportantForAccessibility(accessible); + } + } + + private void updateAccessibilityFlags(CellLayout page, int pageNo) { + if (mState == State.OVERVIEW) { + page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + page.getShortcutsAndWidgets().setImportantForAccessibility( + IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + page.setContentDescription(getPageDescription(pageNo)); + + if (mPagesAccessibilityDelegate == null) { + mPagesAccessibilityDelegate = new OverviewScreenAccessibilityDelegate(this); + } + page.setAccessibilityDelegate(mPagesAccessibilityDelegate); + } else { + int accessible = mState == State.NORMAL ? + IMPORTANT_FOR_ACCESSIBILITY_NO : + IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; + page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + page.getShortcutsAndWidgets().setImportantForAccessibility(accessible); + page.setContentDescription(null); + page.setAccessibilityDelegate(null); + } } @Override @@ -4446,25 +4479,21 @@ public class Workspace extends SmoothPagedView return super.getPageIndicatorMarker(pageIndex); } - @Override - public void syncPages() { - } - - @Override - public void syncPageItems(int page, boolean immediate) { - } - protected String getPageIndicatorDescription() { String settings = getResources().getString(R.string.settings_button_text); return getCurrentPageDescription() + ", " + settings; } protected String getCurrentPageDescription() { - int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; - int delta = numCustomPages(); if (hasCustomContent() && getNextPage() == 0) { return mCustomContentDescription; } + int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; + return getPageDescription(page); + } + + private String getPageDescription(int page) { + int delta = numCustomPages(); return String.format(getContext().getString(R.string.workspace_scroll_format), page + 1 - delta, getChildCount() - delta); } diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java index a0cedeb63..61a64e3f3 100644 --- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java +++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java @@ -24,8 +24,11 @@ import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.view.View; +import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.DecelerateInterpolator; + import com.android.launcher3.util.Thunk; import java.util.HashMap; @@ -190,7 +193,7 @@ public class WorkspaceStateTransitionAnimation { final HashMap<View, Integer> layerViews) { AccessibilityManager am = (AccessibilityManager) mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE); - boolean accessibilityEnabled = am.isEnabled(); + final boolean accessibilityEnabled = am.isEnabled(); // Reinitialize animation arrays for the current workspace state reinitializeAnimationArrays(); @@ -301,7 +304,7 @@ public class WorkspaceStateTransitionAnimation { } final View searchBar = mLauncher.getOrCreateQsbBar(); - final View overviewPanel = mLauncher.getOverviewPanel(); + final ViewGroup overviewPanel = mLauncher.getOverviewPanel(); final View hotseat = mLauncher.getHotseat(); final View pageIndicator = mWorkspace.getPageIndicator(); if (animated) { @@ -424,6 +427,11 @@ public class WorkspaceStateTransitionAnimation { @Override public void onAnimationEnd(Animator animation) { mStateAnimator = null; + + if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) { + overviewPanel.getChildAt(0).performAccessibilityAction( + AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); + } } }); } else { @@ -443,6 +451,11 @@ public class WorkspaceStateTransitionAnimation { mWorkspace.setScaleX(mNewScale); mWorkspace.setScaleY(mNewScale); mWorkspace.setTranslationY(finalWorkspaceTranslationY); + + if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) { + overviewPanel.getChildAt(0).performAccessibilityAction( + AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); + } } if (stateIsNormal) { diff --git a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java new file mode 100644 index 000000000..d3f5230b2 --- /dev/null +++ b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java @@ -0,0 +1,92 @@ +/* + * 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.accessibility; + +import android.content.Context; +import android.os.Bundle; +import android.util.SparseArray; +import android.view.View; +import android.view.View.AccessibilityDelegate; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; + +import com.android.launcher3.R; +import com.android.launcher3.Workspace; + +public class OverviewScreenAccessibilityDelegate extends AccessibilityDelegate { + + private static final int MOVE_BACKWARD = R.id.action_move_screen_backwards; + private static final int MOVE_FORWARD = R.id.action_move_screen_forwards; + + private final SparseArray<AccessibilityAction> mActions = new SparseArray<>(); + private final Workspace mWorkspace; + + public OverviewScreenAccessibilityDelegate(Workspace workspace) { + mWorkspace = workspace; + + Context context = mWorkspace.getContext(); + boolean isRtl = mWorkspace.isLayoutRtl(); + mActions.put(MOVE_BACKWARD, new AccessibilityAction(MOVE_BACKWARD, + context.getText(isRtl ? R.string.action_move_screen_right : + R.string.action_move_screen_left))); + mActions.put(MOVE_FORWARD, new AccessibilityAction(MOVE_FORWARD, + context.getText(isRtl ? R.string.action_move_screen_left : + R.string.action_move_screen_right))); + } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + if (host != null) { + if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS ) { + int index = mWorkspace.indexOfChild(host); + mWorkspace.setCurrentPage(index); + } else if (action == MOVE_FORWARD) { + movePage(mWorkspace.indexOfChild(host) + 1, host); + return true; + } else if (action == MOVE_BACKWARD) { + movePage(mWorkspace.indexOfChild(host) - 1, host); + return true; + } + } + + return super.performAccessibilityAction(host, action, args); + } + + private void movePage(int finalIndex, View view) { + mWorkspace.onStartReordering(); + mWorkspace.removeView(view); + mWorkspace.addView(view, finalIndex); + mWorkspace.onEndReordering(); + mWorkspace.announceForAccessibility(mWorkspace.getContext().getText(R.string.screen_moved)); + + mWorkspace.updateAccessibilityFlags(); + view.performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); + } + + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + + int index = mWorkspace.indexOfChild(host); + if (index < mWorkspace.getChildCount() - 1) { + info.addAction(mActions.get(MOVE_FORWARD)); + } + if (index > mWorkspace.numCustomPages()) { + info.addAction(mActions.get(MOVE_BACKWARD)); + } + } +} diff --git a/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java b/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java new file mode 100644 index 000000000..6f15324c1 --- /dev/null +++ b/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java @@ -0,0 +1,40 @@ +/* + * 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.widget; + +import android.content.Context; +import android.util.AttributeSet; +import com.android.launcher3.BaseContainerRecyclerView; + +/** + * The widgets recycler view container. + */ +public class WidgetsContainerRecyclerView extends BaseContainerRecyclerView { + + public WidgetsContainerRecyclerView(Context context) { + this(context, null); + } + + public WidgetsContainerRecyclerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public WidgetsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + +}
\ No newline at end of file diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 22e29f304..439227f52 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -28,10 +28,9 @@ import android.support.v7.widget.RecyclerView.State; import android.util.AttributeSet; import android.util.Log; import android.view.View; -import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.Toast; - +import com.android.launcher3.BaseContainerView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DragController; @@ -55,8 +54,8 @@ import java.util.ArrayList; /** * The widgets list view container. */ -public class WidgetsContainerView extends FrameLayout implements Insettable, - View.OnLongClickListener, View.OnClickListener, DragSource{ +public class WidgetsContainerView extends BaseContainerView + implements View.OnLongClickListener, View.OnClickListener, DragSource{ private static final String TAG = "WidgetsContainerView"; private static final boolean DEBUG = false; @@ -129,6 +128,7 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, }); mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom()); + onUpdatePaddings(); } // @@ -364,13 +364,17 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, // Container rendering related. // - /* - * @see Insettable#setInsets(Rect) - */ @Override - public void setInsets(Rect insets) { - setPadding(mPadding.left + insets.left, mPadding.top + insets.top, - mPadding.right + insets.right, mPadding.bottom + insets.bottom); + protected void onUpdatePaddings() { + if (mFixedBounds.isEmpty()) { + // If there are no fixed bounds, then use the default padding and insets + setPadding(mPadding.left + mInsets.left, mPadding.top + mInsets.top, + mPadding.right + mInsets.right, mPadding.bottom + mInsets.bottom); + } else { + // If there are fixed bounds, then we update the padding to reflect the fixed bounds. + setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right, + mInsets.bottom); + } } /** diff --git a/src/com/android/launcher3/widget/WidgetsRowView.java b/src/com/android/launcher3/widget/WidgetsRowView.java deleted file mode 100644 index 54667384b..000000000 --- a/src/com/android/launcher3/widget/WidgetsRowView.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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.widget; - -import android.content.Context; -import android.view.MotionEvent; -import android.widget.FrameLayout; -import android.widget.HorizontalScrollView; -import android.widget.TextView; - -import com.android.launcher3.R; - -/** - * Layout used for widget tray rows for each app. For performance, this view can be replaced with - * a {@link RecyclerView} in the future if we settle on scrollable single row for the widgets. - * If we decide on collapsable grid, then HorizontalScrollView can be replaced with a - * {@link GridLayout}. - */ -public class WidgetsRowView extends HorizontalScrollView { - static final String TAG = "WidgetsRow"; - - private Runnable mOnLayoutListener; - private String mAppName; - - public WidgetsRowView(Context context, String appName) { - super(context, null, 0); - mAppName = appName; - } - - /** - * Clears all the key listeners for the individual widgets. - */ - public void resetChildrenOnKeyListeners() { - int childCount = getChildCount(); - for (int j = 0; j < childCount; ++j) { - getChildAt(j).setOnKeyListener(null); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - TextView tv = (TextView) findViewById(R.id.widget_name); - tv.setText(mAppName); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mOnLayoutListener = null; - } - - public void setOnLayoutListener(Runnable r) { - mOnLayoutListener = r; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (mOnLayoutListener != null) { - mOnLayoutListener.run(); - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean result = super.onTouchEvent(event); - return result; - } - - public static class LayoutParams extends FrameLayout.LayoutParams { - public LayoutParams(int width, int height) { - super(width, height); - } - } -} |