summaryrefslogtreecommitdiffstats
path: root/src/com/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android')
-rw-r--r--src/com/android/launcher3/AllAppsList.java11
-rw-r--r--src/com/android/launcher3/AlphabeticalAppsList.java192
-rw-r--r--src/com/android/launcher3/AppsContainerRecyclerView.java61
-rw-r--r--src/com/android/launcher3/AppsContainerSearchEditTextView.java65
-rw-r--r--src/com/android/launcher3/AppsContainerView.java381
-rw-r--r--src/com/android/launcher3/AppsCustomizeCellLayout.java71
-rw-r--r--src/com/android/launcher3/AppsGridAdapter.java147
-rw-r--r--src/com/android/launcher3/AppsListAdapter.java143
-rw-r--r--src/com/android/launcher3/BaseContainerRecyclerView.java113
-rw-r--r--src/com/android/launcher3/BaseContainerView.java100
-rw-r--r--src/com/android/launcher3/CellLayout.java17
-rw-r--r--src/com/android/launcher3/DeviceProfile.java7
-rw-r--r--src/com/android/launcher3/FolderPagedView.java10
-rw-r--r--src/com/android/launcher3/IconCache.java16
-rw-r--r--src/com/android/launcher3/Launcher.java74
-rw-r--r--src/com/android/launcher3/LauncherAccessibilityDelegate.java99
-rw-r--r--src/com/android/launcher3/LauncherModel.java173
-rw-r--r--src/com/android/launcher3/PagedView.java687
-rw-r--r--src/com/android/launcher3/Workspace.java71
-rw-r--r--src/com/android/launcher3/WorkspaceStateTransitionAnimation.java17
-rw-r--r--src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java92
-rw-r--r--src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java40
-rw-r--r--src/com/android/launcher3/widget/WidgetsContainerView.java24
-rw-r--r--src/com/android/launcher3/widget/WidgetsRowView.java90
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);
- }
- }
-}