summaryrefslogtreecommitdiffstats
path: root/src/com/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android')
-rw-r--r--src/com/android/launcher3/AlphabeticalAppsList.java391
-rw-r--r--src/com/android/launcher3/AppInfo.java4
-rw-r--r--src/com/android/launcher3/AppsContainerRecyclerView.java190
-rw-r--r--src/com/android/launcher3/AppsContainerSearchEditTextView.java65
-rw-r--r--src/com/android/launcher3/AppsContainerView.java329
-rw-r--r--src/com/android/launcher3/AppsGridAdapter.java218
-rw-r--r--src/com/android/launcher3/BaseContainerRecyclerView.java133
-rw-r--r--src/com/android/launcher3/BaseContainerView.java101
-rw-r--r--src/com/android/launcher3/BubbleTextView.java82
-rw-r--r--src/com/android/launcher3/CellLayout.java17
-rw-r--r--src/com/android/launcher3/DeviceProfile.java34
-rw-r--r--src/com/android/launcher3/DragLayer.java49
-rw-r--r--src/com/android/launcher3/DragView.java4
-rw-r--r--src/com/android/launcher3/DynamicGrid.java22
-rw-r--r--src/com/android/launcher3/Folder.java33
-rw-r--r--src/com/android/launcher3/FolderIcon.java2
-rw-r--r--src/com/android/launcher3/FolderInfo.java2
-rw-r--r--src/com/android/launcher3/FolderPagedView.java18
-rw-r--r--src/com/android/launcher3/IconCache.java13
-rw-r--r--src/com/android/launcher3/InstallShortcutReceiver.java2
-rw-r--r--src/com/android/launcher3/Launcher.java78
-rw-r--r--src/com/android/launcher3/LauncherAccessibilityDelegate.java99
-rw-r--r--src/com/android/launcher3/LauncherAppWidgetProviderInfo.java2
-rw-r--r--src/com/android/launcher3/LauncherCallbacks.java2
-rw-r--r--src/com/android/launcher3/LauncherExtension.java6
-rw-r--r--src/com/android/launcher3/LauncherModel.java55
-rw-r--r--src/com/android/launcher3/LauncherStateTransitionAnimation.java6
-rw-r--r--src/com/android/launcher3/ShortcutInfo.java8
-rw-r--r--src/com/android/launcher3/Utilities.java15
-rw-r--r--src/com/android/launcher3/Workspace.java7
-rw-r--r--src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java16
-rw-r--r--src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java2
-rw-r--r--src/com/android/launcher3/compat/AlphabeticIndexCompat.java33
-rw-r--r--src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java2
-rw-r--r--src/com/android/launcher3/widget/PackageItemInfo.java2
-rw-r--r--src/com/android/launcher3/widget/WidgetCell.java22
-rw-r--r--src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java58
-rw-r--r--src/com/android/launcher3/widget/WidgetsContainerView.java24
-rw-r--r--src/com/android/launcher3/widget/WidgetsListAdapter.java49
39 files changed, 1531 insertions, 664 deletions
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
index 477c00fe8..dc75637e5 100644
--- a/src/com/android/launcher3/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/AlphabeticalAppsList.java
@@ -3,6 +3,7 @@ package com.android.launcher3;
import android.content.ComponentName;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
+import android.util.Log;
import com.android.launcher3.compat.AlphabeticIndexCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
@@ -13,24 +14,28 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
/**
* A private class to manage access to an app name comparator.
*/
class AppNameComparator {
- private UserManagerCompat mUserManager;
- private Comparator<AppInfo> mAppNameComparator;
+ private final UserManagerCompat mUserManager;
+ private final Collator mCollator;
+ private final Comparator<AppInfo> mAppInfoComparator;
+ private final Comparator<String> mSectionNameComparator;
private HashMap<UserHandleCompat, Long> mUserSerialCache = new HashMap<>();
public AppNameComparator(Context context) {
- final Collator collator = Collator.getInstance();
+ mCollator = Collator.getInstance();
mUserManager = UserManagerCompat.getInstance(context);
- mAppNameComparator = new Comparator<AppInfo>() {
+ mAppInfoComparator = new Comparator<AppInfo>() {
public final int compare(AppInfo a, AppInfo b) {
- // Order by the title
- int result = collator.compare(a.title.toString().trim(),
- b.title.toString().trim());
+ // Order by the title in the current locale
+ int result = compareTitles(a.title.toString(), b.title.toString());
if (result == 0) {
// If two apps have the same title, then order by the component name
result = a.componentName.compareTo(b.componentName);
@@ -49,15 +54,45 @@ class AppNameComparator {
return result;
}
};
+ mSectionNameComparator = new Comparator<String>() {
+ @Override
+ public int compare(String o1, String o2) {
+ return compareTitles(o1, o2);
+ }
+ };
}
/**
* Returns a locale-aware comparator that will alphabetically order a list of applications.
*/
- public Comparator<AppInfo> getComparator() {
+ public Comparator<AppInfo> getAppInfoComparator() {
// Clear the user serial cache so that we get serials as needed in the comparator
mUserSerialCache.clear();
- return mAppNameComparator;
+ return mAppInfoComparator;
+ }
+
+ /**
+ * Returns a locale-aware comparator that will alphabetically order a list of section names.
+ */
+ public Comparator<String> getSectionNameComparator() {
+ return mSectionNameComparator;
+ }
+
+ /**
+ * Compares two titles with the same return value semantics as Comparator.
+ */
+ private int compareTitles(String titleA, String titleB) {
+ // Ensure that we de-prioritize any titles that don't start with a linguistic letter or digit
+ boolean aStartsWithLetter = Character.isLetterOrDigit(titleA.codePointAt(0));
+ boolean bStartsWithLetter = Character.isLetterOrDigit(titleB.codePointAt(0));
+ if (aStartsWithLetter && !bStartsWithLetter) {
+ return -1;
+ } else if (!aStartsWithLetter && bStartsWithLetter) {
+ return 1;
+ }
+
+ // Order by the title in the current locale
+ return mCollator.compare(titleA, titleB);
}
/**
@@ -78,21 +113,37 @@ class AppNameComparator {
*/
public class AlphabeticalAppsList {
+ public static final String TAG = "AlphabeticalAppsList";
+ private static final boolean DEBUG = false;
+
/**
* 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;
- // The section AdapterItem for this section
- public AdapterItem sectionItem;
+ 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;
}
}
@@ -100,34 +151,48 @@ 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;
+ // Whether or not this is a predicted app
+ public boolean isPredictedApp;
+
+ 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;
+ section.sectionBreakItem = item;
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,
+ boolean isPredictedApp) {
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;
+ item.isPredictedApp = isPredictedApp;
return item;
}
}
@@ -136,25 +201,76 @@ public class AlphabeticalAppsList {
* A filter interface to limit the set of applications in the apps list.
*/
public interface Filter {
- public boolean retainApp(AppInfo info, String sectionName);
+ boolean retainApp(AppInfo info, String sectionName);
+ }
+
+ /**
+ * Common interface for different merging strategies.
+ */
+ private interface MergeAlgorithm {
+ boolean continueMerging(int sectionAppCount, int numAppsPerRow, int mergeCount);
+ }
+
+ /**
+ * The logic we use to merge sections on tablets.
+ */
+ private static class TabletMergeAlgorithm implements MergeAlgorithm {
+
+ @Override
+ public boolean continueMerging(int sectionAppCount, int numAppsPerRow, int mergeCount) {
+ // Merge EVERYTHING
+ return true;
+ }
+ }
+
+ /**
+ * The logic we use to merge sections on phones.
+ */
+ private static class PhoneMergeAlgorithm implements MergeAlgorithm {
+
+ private int mMinAppsPerRow;
+ private int mMinRowsInMergedSection;
+ private int mMaxAllowableMerges;
+
+ public PhoneMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges) {
+ mMinAppsPerRow = minAppsPerRow;
+ mMinRowsInMergedSection = minRowsInMergedSection;
+ mMaxAllowableMerges = maxNumMerges;
+ }
+
+ @Override
+ public boolean continueMerging(int sectionAppCount, int numAppsPerRow, int mergeCount) {
+ // Continue merging if the number of hanging apps on the final row is less than some
+ // fixed number (ragged), the merged rows has yet to exceed some minimum row count,
+ // and while the number of merged sections is less than some fixed number of merges
+ int rows = sectionAppCount / numAppsPerRow;
+ int cols = sectionAppCount % numAppsPerRow;
+ return (0 < cols && cols < mMinAppsPerRow) &&
+ rows < mMinRowsInMergedSection &&
+ mergeCount < mMaxAllowableMerges;
+ }
}
- // The maximum number of rows allowed in a merged section before we stop merging
- private static final int MAX_ROWS_IN_MERGED_SECTION = Integer.MAX_VALUE;
+ private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
+ private static final int MAX_NUM_MERGES_PHONE = 2;
+ private Context mContext;
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 List<ComponentName> mPredictedApps = new ArrayList<>();
+ private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
private RecyclerView.Adapter mAdapter;
private Filter mFilter;
private AlphabeticIndexCompat mIndexer;
private AppNameComparator mAppNameComparator;
+ private MergeAlgorithm mMergeAlgorithm;
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, int numAppsPerRow) {
+ mContext = context;
mIndexer = new AlphabeticIndexCompat(context);
mAppNameComparator = new AppNameComparator(context);
setNumAppsPerRow(numAppsPerRow);
@@ -165,7 +281,16 @@ public class AlphabeticalAppsList {
*/
public void setNumAppsPerRow(int numAppsPerRow) {
mNumAppsPerRow = numAppsPerRow;
- mMaxAllowableMerges = (int) Math.ceil(numAppsPerRow / 2f);
+
+ // Update the merge algorithm
+ DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
+ if (grid.isPhone()) {
+ mMergeAlgorithm = new PhoneMergeAlgorithm((int) Math.ceil(numAppsPerRow / 2f),
+ MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
+ } else {
+ mMergeAlgorithm = new TabletMergeAlgorithm();
+ }
+
onAppsUpdated();
}
@@ -184,6 +309,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() {
@@ -223,10 +355,20 @@ public class AlphabeticalAppsList {
}
/**
+ * Sets the current set of predicted apps. Since this can be called before we get the full set
+ * of applications, we should merge the results only in onAppsUpdated() which is idempotent.
+ */
+ public void setPredictedApps(List<ComponentName> apps) {
+ mPredictedApps.clear();
+ mPredictedApps.addAll(apps);
+ 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();
@@ -241,6 +383,8 @@ public class AlphabeticalAppsList {
for (AppInfo info : apps) {
addApp(info);
}
+ onAppsUpdated();
+ mAdapter.notifyDataSetChanged();
}
/**
@@ -251,12 +395,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();
}
/**
@@ -267,10 +411,10 @@ public class AlphabeticalAppsList {
int removeIndex = findAppByComponent(mApps, info);
if (removeIndex != -1) {
mApps.remove(removeIndex);
- onAppsUpdated();
- mAdapter.notifyDataSetChanged();
}
}
+ onAppsUpdated();
+ mAdapter.notifyDataSetChanged();
}
/**
@@ -290,14 +434,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());
+ int index = Collections.binarySearch(mApps, info, mAppNameComparator.getAppInfoComparator());
if (index < 0) {
mApps.add(-(index + 1), info);
- onAppsUpdated();
- mAdapter.notifyDataSetChanged();
}
}
@@ -305,89 +447,166 @@ public class AlphabeticalAppsList {
* Updates internals when the set of apps are updated.
*/
private void onAppsUpdated() {
- // Recreate the filtered and sectioned apps (for convenience for the grid layout)
+ // Sort the list of apps
+ Collections.sort(mApps, mAppNameComparator.getAppInfoComparator());
+
+ // As a special case for some languages (currently only Simplified Chinese), we may need to
+ // coalesce sections
+ Locale curLocale = mContext.getResources().getConfiguration().locale;
+ TreeMap<String, ArrayList<AppInfo>> sectionMap = null;
+ boolean localeRequiresSectionSorting = curLocale.equals(Locale.SIMPLIFIED_CHINESE);
+ if (localeRequiresSectionSorting) {
+ // Compute the section headers. We use a TreeMap with the section name comparator to
+ // ensure that the sections are ordered when we iterate over it later
+ sectionMap = new TreeMap<>(mAppNameComparator.getSectionNameComparator());
+ for (AppInfo info : mApps) {
+ // Add the section to the cache
+ String sectionName = mCachedSectionNames.get(info.title);
+ if (sectionName == null) {
+ sectionName = mIndexer.computeSectionName(info.title);
+ mCachedSectionNames.put(info.title, sectionName);
+ }
+
+ // Add it to the mapping
+ ArrayList<AppInfo> sectionApps = sectionMap.get(sectionName);
+ if (sectionApps == null) {
+ sectionApps = new ArrayList<>();
+ sectionMap.put(sectionName, sectionApps);
+ }
+ sectionApps.add(info);
+ }
+ } else {
+ // Just compute the section headers for use below
+ for (AppInfo info : mApps) {
+ // Add the section to the cache
+ String sectionName = mCachedSectionNames.get(info.title);
+ if (sectionName == null) {
+ sectionName = mIndexer.computeSectionName(info.title);
+ mCachedSectionNames.put(info.title, sectionName);
+ }
+ }
+ }
+
+ // Prepare to update the list of sections, filtered apps, etc.
mFilteredApps.clear();
mSections.clear();
mSectionedFilteredApps.clear();
+ mFastScrollerSections.clear();
SectionInfo lastSectionInfo = null;
+ String lastSectionName = null;
+ FastScrollSectionInfo lastFastScrollerSectionInfo = null;
int position = 0;
int appIndex = 0;
- for (AppInfo info : mApps) {
- String sectionName = mIndexer.computeSectionName(info.title.toString().trim());
+ List<AppInfo> allApps = new ArrayList<>();
+
+ // Add the predicted apps to the combined list
+ int numPredictedApps = 0;
+ if (mPredictedApps != null && !mPredictedApps.isEmpty() && !hasFilter()) {
+ for (ComponentName cn : mPredictedApps) {
+ for (AppInfo info : mApps) {
+ if (cn.equals(info.componentName)) {
+ allApps.add(info);
+ numPredictedApps++;
+ break;
+ }
+ }
+ // Stop at the number of predicted apps
+ if (numPredictedApps == mNumAppsPerRow) {
+ break;
+ }
+ }
+ }
+
+ // Add all the other apps to the combined list
+ if (localeRequiresSectionSorting) {
+ for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
+ allApps.addAll(entry.getValue());
+ }
+ } else {
+ allApps.addAll(mApps);
+ }
+
+ // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
+ // ordered set of sections
+ int numApps = allApps.size();
+ for (int i = 0; i < numApps; i++) {
+ boolean isPredictedApp = i < numPredictedApps;
+ AppInfo info = allApps.get(i);
+ // The section name was computed above so this should be find
+ String sectionName = isPredictedApp ? "" : mCachedSectionNames.get(info.title);
// Check if we want to retain this app
if (mFilter != null && !mFilter.retainApp(info, sectionName)) {
continue;
}
- // Create a new section if necessary
- if (lastSectionInfo == null || !lastSectionInfo.sectionName.equals(sectionName)) {
- lastSectionInfo = new SectionInfo(sectionName);
+ // Create a new section if the section names do not match
+ if (lastSectionInfo == null ||
+ (!isPredictedApp && !sectionName.equals(lastSectionName))) {
+ lastSectionName = sectionName;
+ lastSectionInfo = new SectionInfo();
+ lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName,
+ (float) appIndex / numApps);
mSections.add(lastSectionInfo);
+ mFastScrollerSections.add(lastFastScrollerSectionInfo);
- // Create a new section item, this item is used to break the flow of items in the
- // list
- AdapterItem sectionItem = AdapterItem.asSection(position++, sectionName);
- if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS && !hasFilter()) {
- lastSectionInfo.sectionItem = sectionItem;
+ // Create a new section item to break the flow of items in the list
+ if (!hasFilter()) {
+ AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
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++, isPredictedApp);
if (lastSectionInfo.firstAppItem == null) {
lastSectionInfo.firstAppItem = appItem;
+ lastFastScrollerSectionInfo.appItem = appItem;
}
mSectionedFilteredApps.add(appItem);
mFilteredApps.add(info);
}
+ // Go through each section and try and merge some of the sections
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);
- String mergedSectionName = section.sectionName;
- sectionAppCount = section.numAppsInSection;
+ 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 &&
- (int) Math.ceil(sectionAppCount / mNumAppsPerRow) < MAX_ROWS_IN_MERGED_SECTION &&
+
+ // Merge rows based on the current strategy
+ while (mMergeAlgorithm.continueMerging(sectionAppCount, mNumAppsPerRow, mergeCount) &&
(i + 1) < mSections.size()) {
SectionInfo nextSection = mSections.remove(i + 1);
- // Merge the section names
- if (AppsContainerView.GRID_MERGE_SECTION_HEADERS) {
- mergedSectionName += nextSection.sectionName;
- }
+
// Remove the next section break
- mSectionedFilteredApps.remove(nextSection.sectionItem);
- if (AppsContainerView.GRID_MERGE_SECTION_HEADERS) {
- // Update the section names for the two sections
- int pos = mSectionedFilteredApps.indexOf(section.firstAppItem);
- for (int j = pos; j < (pos + section.numAppsInSection + nextSection.numAppsInSection); j++) {
- AdapterItem item = mSectionedFilteredApps.get(j);
- item.sectionName = mergedSectionName;
- }
+ 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
- int pos = mSectionedFilteredApps.indexOf(nextSection.firstAppItem);
+
+ // 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.numAppsInSection += nextSection.numAppsInSection;
- sectionAppCount += nextSection.numAppsInSection;
- mergeCount++;
- if (mergeCount >= mMaxAllowableMerges) {
- break;
+ section.numApps += nextSection.numApps;
+ sectionAppCount += nextSection.numApps;
+
+ if (DEBUG) {
+ Log.d(TAG, "Merging: " + nextSection.firstAppItem.sectionName +
+ " to " + section.firstAppItem.sectionName +
+ " mergedNumRows: " + (sectionAppCount / mNumAppsPerRow));
}
+ mergeCount++;
}
}
}
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 7c6b0664c..58a57a1fe 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -105,7 +105,7 @@ public class AppInfo extends ItemInfo {
public AppInfo(AppInfo info) {
super(info);
componentName = info.componentName;
- title = info.title.toString();
+ title = Utilities.trim(info.title);
intent = new Intent(info.intent);
flags = info.flags;
firstInstallTime = info.firstInstallTime;
@@ -114,7 +114,7 @@ public class AppInfo extends ItemInfo {
@Override
public String toString() {
- return "ApplicationInfo(title=" + title.toString() + " id=" + this.id
+ return "ApplicationInfo(title=" + title + " id=" + this.id
+ " type=" + this.itemType + " container=" + this.container
+ " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY
+ " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java
index 7f64be2f5..e918bc2ee 100644
--- a/src/com/android/launcher3/AppsContainerRecyclerView.java
+++ b/src/com/android/launcher3/AppsContainerRecyclerView.java
@@ -30,23 +30,32 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
-import com.android.launcher3.util.Thunk;
-
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;
- private static final int SCROLL_DELTA_THRESHOLD = 4;
+ /**
+ * The current scroll state of the recycler view. We use this in updateVerticalScrollbarBounds()
+ * and scrollToPositionAtProgress() to determine the scroll position of the recycler view so
+ * that we can calculate what the scroll bar looks like, and where to jump to from the fast
+ * scroller.
+ */
+ private static class ScrollPositionState {
+ // The index of the first app in the row (Note that is this not the position)
+ int rowFirstAppIndex;
+ // The index of the first visible row
+ int rowIndex;
+ // The offset of the first visible row
+ int rowTopOffset;
+ // The height of a given row (they are currently all the same height)
+ int rowHeight;
+ }
- /** Keeps the last known scrolling delta/velocity along y-axis. */
- @Thunk int mDy = 0;
- private float mDeltaThreshold;
+ private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
private AlphabeticalAppsList mApps;
private int mNumAppsPerRow;
@@ -66,7 +75,8 @@ public class AppsContainerRecyclerView extends RecyclerView
private int mScrollbarWidth;
private int mScrollbarMinHeight;
private int mScrollbarInset;
- private RecyclerView.OnScrollListener mScrollListenerProxy;
+ private Rect mBackgroundPadding = new Rect();
+ private ScrollPositionState mScrollPosState = new ScrollPositionState();
public AppsContainerRecyclerView(Context context) {
this(context, null);
@@ -100,21 +110,7 @@ public class AppsContainerRecyclerView extends RecyclerView
mScrollbarInset =
res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_scrubber_touch_inset);
setFastScrollerAlpha(getFastScrollerAlpha());
- mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD;
-
- ScrollListener listener = new ScrollListener();
- setOnScrollListener(listener);
- }
-
- private class ScrollListener extends RecyclerView.OnScrollListener {
- public ScrollListener() {
- }
-
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- mDy = dy;
- mScrollListenerProxy.onScrolled(recyclerView, dx, dy);
- }
+ setOverScrollMode(View.OVER_SCROLL_NEVER);
}
/**
@@ -131,11 +127,10 @@ public class AppsContainerRecyclerView extends RecyclerView
mNumAppsPerRow = rowSize;
}
- /**
- * Sets an additional scroll listener, not necessary in master support lib.
- */
- public void setOnScrollListenerProxy(RecyclerView.OnScrollListener listener) {
- mScrollListenerProxy = listener;
+ @Override
+ public void setBackground(Drawable background) {
+ super.setBackground(background);
+ background.getPadding(mBackgroundPadding);
}
/**
@@ -160,6 +155,14 @@ public class AppsContainerRecyclerView extends RecyclerView
return mScrollbarWidth;
}
+ /**
+ * Scrolls this recycler view to the top.
+ */
+ public void scrollToTop() {
+ scrollToPosition(0);
+ updateScrollY(0);
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -187,10 +190,6 @@ public class AppsContainerRecyclerView extends RecyclerView
handleTouchEvent(ev);
}
- public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
- }
-
/**
* Handles the touch event and determines whether to show the fast scroller (or updates it if
* it is already showing).
@@ -206,8 +205,7 @@ public class AppsContainerRecyclerView extends RecyclerView
// Keep track of the down positions
mDownX = mLastX = x;
mDownY = mLastY = y;
- if ((Math.abs(mDy) < mDeltaThreshold &&
- getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) {
+ if (shouldStopScroll(ev)) {
stopScroll();
}
break;
@@ -265,7 +263,7 @@ public class AppsContainerRecyclerView extends RecyclerView
* Draws the fast scroller popup.
*/
private void drawFastScrollerPopup(Canvas canvas) {
- if (mFastScrollAlpha > 0f) {
+ if (mFastScrollAlpha > 0f && !mFastScrollSectionName.isEmpty()) {
int x;
int y;
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
@@ -274,7 +272,7 @@ public class AppsContainerRecyclerView extends RecyclerView
// Calculate the position for the fast scroller popup
Rect bgBounds = mFastScrollerBg.getBounds();
if (isRtl) {
- x = getPaddingLeft() + getScrollBarSize();
+ x = mBackgroundPadding.left + getScrollBarSize();
} else {
x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width();
}
@@ -290,8 +288,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);
@@ -316,43 +315,47 @@ public class AppsContainerRecyclerView extends RecyclerView
* Invalidates the fast scroller popup.
*/
private void invalidateFastScroller() {
- invalidate(getWidth() - getPaddingRight() - getScrollBarSize() -
+ invalidate(getWidth() - mBackgroundPadding.right - getScrollBarSize() -
mFastScrollerBg.getIntrinsicWidth(), 0, getWidth(), getHeight());
}
/**
- * 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
- List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
- 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 = items.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);
+
+ // We need to workaround the RecyclerView to get the right scroll position after scrolling
+ List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+ getCurScrollState(mScrollPosState, items);
+ if (mScrollPosState.rowIndex != -1) {
+ int rowIndex = findRowForAppIndex(mScrollPosState.rowFirstAppIndex);
+ int y = (rowIndex * mScrollPosState.rowHeight) - mScrollPosState.rowTopOffset;
+ updateScrollY(y);
+ }
- // Return the section name of the row
- return lastSectionInfo.sectionName;
+ return lastScrollSection.sectionName;
}
/**
@@ -372,44 +375,29 @@ public class AppsContainerRecyclerView extends RecyclerView
int y;
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
LAYOUT_DIRECTION_RTL);
- int rowIndex = -1;
- int rowTopOffset = -1;
- int rowHeight = -1;
int rowCount = getNumRows();
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- int position = getChildPosition(child);
- if (position != NO_POSITION) {
- AlphabeticalAppsList.AdapterItem item = items.get(position);
- if (!item.isSectionHeader) {
- rowIndex = findRowForAppIndex(item.appIndex);
- rowTopOffset = getLayoutManager().getDecoratedTop(child);
- rowHeight = child.getHeight();
- break;
- }
- }
- }
+ getCurScrollState(mScrollPosState, items);
- if (rowIndex != -1) {
+ if (mScrollPosState.rowIndex != -1) {
int height = getHeight() - getPaddingTop() - getPaddingBottom();
- int totalScrollHeight = rowCount * rowHeight;
+ int totalScrollHeight = rowCount * mScrollPosState.rowHeight;
if (totalScrollHeight > height) {
int scrollbarHeight = Math.max(mScrollbarMinHeight,
(int) (height / ((float) totalScrollHeight / height)));
// Calculate the position and size of the scroll bar
if (isRtl) {
- x = getPaddingLeft();
+ x = mBackgroundPadding.left;
} else {
- x = getWidth() - getPaddingRight() - mScrollbarWidth;
+ x = getWidth() - mBackgroundPadding.right - mScrollbarWidth;
}
// To calculate the offset, we compute the percentage of the total scrollable height
// that the user has already scrolled and then map that to the scroll bar bounds
int availableY = totalScrollHeight - height;
int availableScrollY = height - scrollbarHeight;
- y = (rowIndex * rowHeight) - rowTopOffset;
+ y = (mScrollPosState.rowIndex * mScrollPosState.rowHeight) -
+ mScrollPosState.rowTopOffset;
y = getPaddingTop() +
(int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY);
@@ -421,18 +409,18 @@ public class AppsContainerRecyclerView extends RecyclerView
}
/**
- * Returns the row index for a given position in the list.
+ * Returns the row index for a app index in the list.
*/
- private int findRowForAppIndex(int position) {
+ private int findRowForAppIndex(int index) {
List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
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) {
- return rowCount + ((position - appIndex) / mNumAppsPerRow);
+ int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow);
+ if (appIndex + info.numApps > index) {
+ return rowCount + ((index - appIndex) / mNumAppsPerRow);
}
- appIndex += info.numAppsInSection;
+ appIndex += info.numApps;
rowCount += numRowsInSection;
}
return appIndex;
@@ -445,9 +433,35 @@ 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;
}
+
+ /**
+ * Returns the current scroll state.
+ */
+ private void getCurScrollState(ScrollPositionState stateOut,
+ List<AlphabeticalAppsList.AdapterItem> items) {
+ stateOut.rowFirstAppIndex = -1;
+ stateOut.rowIndex = -1;
+ stateOut.rowTopOffset = -1;
+ stateOut.rowHeight = -1;
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ int position = getChildPosition(child);
+ if (position != NO_POSITION) {
+ AlphabeticalAppsList.AdapterItem item = items.get(position);
+ if (!item.isSectionHeader) {
+ stateOut.rowFirstAppIndex = item.appIndex;
+ stateOut.rowIndex = findRowForAppIndex(item.appIndex);
+ stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
+ stateOut.rowHeight = child.getHeight();
+ break;
+ }
+ }
+ }
+ }
}
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 9122427fd..5dac9f1e8 100644
--- a/src/com/android/launcher3/AppsContainerView.java
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3;
+import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
@@ -31,30 +32,35 @@ 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;
+import java.util.regex.Pattern;
/**
- * 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,
+public class AppsContainerView extends BaseContainerView implements DragSource, Insettable,
+ TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener,
View.OnClickListener, View.OnLongClickListener {
public static final boolean GRID_MERGE_SECTIONS = true;
- public static final boolean GRID_MERGE_SECTION_HEADERS = false;
public static final boolean GRID_HIDE_SECTION_HEADERS = false;
private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
- private static final boolean DYNAMIC_HEADER_ELEVATION = false;
+ private static final boolean DYNAMIC_HEADER_ELEVATION = true;
+ private static final boolean DISMISS_SEARCH_ON_BACK = true;
private static final float HEADER_ELEVATION_DP = 4;
+ // How far the user has to scroll in order to reach the full elevation
+ private static final float HEADER_SCROLL_TO_ELEVATION_DP = 16;
private static final int FADE_IN_DURATION = 175;
- private static final int FADE_OUT_DURATION = 125;
+ private static final int FADE_OUT_DURATION = 100;
+ private static final int SEARCH_TRANSLATION_X_DP = 18;
+
+ private static final Pattern SPLIT_PATTERN = Pattern.compile("[\\s|\\p{javaSpaceChar}]+");
@Thunk Launcher mLauncher;
@Thunk AlphabeticalAppsList mApps;
@@ -68,18 +74,14 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett
private View mSearchBarContainerView;
private View mSearchButtonView;
private View mDismissSearchButtonView;
- private EditText mSearchBarEditView;
+ 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;
@@ -99,8 +101,6 @@ 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;
mNumAppsPerRow = grid.appsViewNumCols;
mApps = new AlphabeticalAppsList(context, mNumAppsPerRow);
@@ -114,6 +114,13 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett
}
/**
+ * Sets the current set of predicted apps.
+ */
+ public void setPredictedApps(List<ComponentName> apps) {
+ mApps.setPredictedApps(apps);
+ }
+
+ /**
* Sets the current set of apps.
*/
public void setApps(List<AppInfo> apps) {
@@ -146,16 +153,15 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett
*/
public void hideHeaderBar() {
mHeaderView.setVisibility(View.GONE);
- updateBackgrounds();
- updatePaddings();
+ onUpdateBackgrounds();
+ onUpdatePaddings();
}
/**
* Scrolls this list view to the top.
*/
public void scrollToTop() {
- mAppsRecyclerView.scrollToPosition(0);
- mRecyclerViewScrollY = 0;
+ mAppsRecyclerView.scrollToTop();
}
/**
@@ -199,10 +205,25 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett
mSearchBarContainerView = findViewById(R.id.app_search_container);
mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
mDismissSearchButtonView.setOnClickListener(this);
- mSearchBarEditView = (EditText) findViewById(R.id.app_search_box);
+ 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() {
+ // Only hide the search field if there is no query, or if there
+ // are no filtered results
+ String query = Utilities.trim(
+ mSearchBarEditView.getEditableText().toString());
+ if (query.isEmpty() || mApps.hasNoFilteredResults()) {
+ hideSearchField(true, true);
+ }
+ }
+ });
+ }
}
mAppsRecyclerView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view);
mAppsRecyclerView.setApps(mApps);
@@ -210,61 +231,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();
- }
- });
+ mAppsRecyclerView.setOnScrollListenerProxy(
+ new BaseContainerRecyclerView.OnScrollToListener() {
+ @Override
+ public void onScrolledTo(int x, int y) {
+ mRecyclerViewScrollY = y;
+ 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);
- mAdapter.setNumAppsPerRow(mNumAppsPerRow);
- mApps.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 (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
- mFixedBounds.top = mInsets.top;
- mFixedBounds.bottom = getMeasuredHeight();
- }
+ 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,
+ mFixedBounds.bottom);
+ }
+
+ // Update the apps recycler view, inset it by the container inset as well
+ DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
+ int startMargin = grid.isPhone() ? mContentMarginStart : 0;
+ int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
+ if (isRtl) {
+ mAppsRecyclerView.setPadding(inset + mAppsRecyclerView.getScrollbarWidth(), inset,
+ inset + startMargin, inset);
+ } else {
+ mAppsRecyclerView.setPadding(inset + startMargin, inset,
+ inset + mAppsRecyclerView.getScrollbarWidth(), inset);
+ }
+
+ // Update the header bar
+ if (hasSearchBar) {
+ LinearLayout.LayoutParams lp =
+ (LinearLayout.LayoutParams) mHeaderView.getLayoutParams();
+ lp.leftMargin = lp.rightMargin = inset;
}
- // 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() {
- 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
@@ -392,27 +448,40 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett
@Override
public void afterTextChanged(final Editable s) {
- if (s.toString().isEmpty()) {
+ String queryText = s.toString();
+ if (queryText.isEmpty()) {
mApps.setFilter(null);
} else {
String formatStr = getResources().getString(R.string.apps_view_no_search_results);
- mAdapter.setEmptySearchText(String.format(formatStr, s.toString()));
+ mAdapter.setEmptySearchText(String.format(formatStr, queryText));
- final String filterText = s.toString().toLowerCase().replaceAll("\\s+", "");
+ // Do an intersection of the words in the query and each title, and filter out all the
+ // apps that don't match all of the words in the query.
+ final String queryTextLower = queryText.toLowerCase();
+ final String[] queryWords = SPLIT_PATTERN.split(queryTextLower);
mApps.setFilter(new AlphabeticalAppsList.Filter() {
@Override
public boolean retainApp(AppInfo info, String sectionName) {
- String title = info.title.toString();
- if (sectionName.toLowerCase().contains(filterText)) {
+ if (sectionName.toLowerCase().contains(queryTextLower)) {
return true;
}
- String[] words = title.toLowerCase().split("\\s+");
- for (int i = 0; i < words.length; i++) {
- if (words[i].startsWith(filterText)) {
- return true;
+ String title = info.title.toString();
+ String[] words = SPLIT_PATTERN.split(title.toLowerCase());
+ for (int qi = 0; qi < queryWords.length; qi++) {
+ boolean foundMatch = false;
+ for (int i = 0; i < words.length; i++) {
+ if (words[i].startsWith(queryWords[qi])) {
+ foundMatch = true;
+ break;
+ }
+ }
+ if (!foundMatch) {
+ // If there is a word in the query that does not match any words in this
+ // title, so skip it.
+ return false;
}
}
- return false;
+ return true;
}
});
}
@@ -473,11 +542,16 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett
* 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);
+ if (DYNAMIC_HEADER_ELEVATION && Utilities.isLmpOrAbove()) {
+ int elevation = DynamicGrid.pxFromDp(HEADER_ELEVATION_DP,
+ getContext().getResources().getDisplayMetrics());
+ int scrollToElevation = DynamicGrid.pxFromDp(HEADER_SCROLL_TO_ELEVATION_DP,
+ getContext().getResources().getDisplayMetrics());
+ float elevationPct = (float) Math.min(mRecyclerViewScrollY, scrollToElevation) /
+ scrollToElevation;
+ float newElevation = elevation * elevationPct;
+ if (Float.compare(mHeaderView.getElevation(), newElevation) != 0) {
+ mHeaderView.setElevation(newElevation);
}
}
}
@@ -531,72 +605,20 @@ 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.
- */
- private void updatePaddings() {
- boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
- LAYOUT_DIRECTION_RTL);
- boolean hasSearchBar = (mSearchBarEditView != null) &&
- (mSearchBarEditView.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);
- }
-
- // Update the apps recycler view
- 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
- if (hasSearchBar) {
- LinearLayout.LayoutParams lp =
- (LinearLayout.LayoutParams) mHeaderView.getLayoutParams();
- lp.leftMargin = lp.rightMargin = inset;
- }
- }
-
- /**
- * Update the background of the Apps view and children.
- */
- private void updateBackgrounds() {
- 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));
- }
-
- /**
* Shows the search field.
*/
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.animate().alpha(1f).setDuration(FADE_IN_DURATION).withLayer()
+ mSearchBarContainerView.setTranslationX(translationX);
+ mSearchBarContainerView.animate()
+ .alpha(1f)
+ .translationX(0)
+ .setDuration(FADE_IN_DURATION)
+ .withLayer()
.withEndAction(new Runnable() {
@Override
public void run() {
@@ -605,38 +627,57 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett
InputMethodManager.SHOW_IMPLICIT);
}
});
- mSearchButtonView.animate().alpha(0f).setDuration(FADE_OUT_DURATION).withLayer();
+ mSearchButtonView.animate()
+ .alpha(0f)
+ .translationX(-translationX)
+ .setDuration(FADE_OUT_DURATION)
+ .withLayer();
}
/**
* 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).setDuration(FADE_IN_DURATION).withLayer()
+ mSearchBarContainerView.animate()
+ .alpha(0f)
+ .translationX(0)
+ .setDuration(FADE_IN_DURATION)
+ .withLayer()
.withEndAction(new Runnable() {
@Override
public void run() {
mSearchBarContainerView.setVisibility(View.INVISIBLE);
- mSearchBarEditView.setText("");
+ if (resetTextField) {
+ mSearchBarEditView.setText("");
+ }
mApps.setFilter(null);
if (returnFocusToRecyclerView) {
mAppsRecyclerView.requestFocus();
}
- scrollToTop();
}
});
- mSearchButtonView.animate().alpha(1f).setDuration(FADE_OUT_DURATION).withLayer();
+ mSearchButtonView.setTranslationX(-translationX);
+ mSearchButtonView.animate()
+ .alpha(1f)
+ .translationX(0)
+ .setDuration(FADE_OUT_DURATION)
+ .withLayer();
} else {
mSearchBarContainerView.setVisibility(View.INVISIBLE);
- mSearchBarEditView.setText("");
+ if (resetTextField) {
+ mSearchBarEditView.setText("");
+ }
mApps.setFilter(null);
mSearchButtonView.setAlpha(1f);
+ mSearchButtonView.setTranslationX(0f);
if (returnFocusToRecyclerView) {
mAppsRecyclerView.requestFocus();
}
- scrollToTop();
}
getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
}
diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java
index 62d9129c9..4014e3804 100644
--- a/src/com/android/launcher3/AppsGridAdapter.java
+++ b/src/com/android/launcher3/AppsGridAdapter.java
@@ -4,11 +4,10 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
-import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -36,13 +35,11 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
public View mContent;
- public boolean mIsSectionHeader;
public boolean mIsEmptyRow;
- public ViewHolder(View v, boolean isSectionHeader, boolean isEmptyRow) {
+ public ViewHolder(View v, boolean isEmptyRow) {
super(v);
mContent = v;
- mIsSectionHeader = isSectionHeader;
mIsEmptyRow = isEmptyRow;
}
}
@@ -66,11 +63,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
if (mApps.getAdapterItems().get(position).isSectionHeader) {
// Section break spans full width
- if (AppsContainerView.GRID_HIDE_SECTION_HEADERS) {
- return 0;
- } else {
- return mAppsPerRow;
- }
+ return mAppsPerRow;
} else {
return 1;
}
@@ -84,7 +77,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
private static final boolean FADE_OUT_SECTIONS = false;
- private HashMap<String, Point> mCachedSectionBounds = new HashMap<>();
+ private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>();
private Rect mTmpBounds = new Rect();
@Override
@@ -93,76 +86,91 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
return;
}
+ DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
- String lastSectionName = null;
- int appIndexInSection = 0;
+ boolean hasDrawnPredictedAppDivider = false;
+ int childCount = parent.getChildCount();
int lastSectionTop = 0;
int lastSectionHeight = 0;
- for (int i = 0; i < parent.getChildCount(); i++) {
+ for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child);
- if (shouldDrawItemSection(holder, child, i, items)) {
- int cellTopOffset = (2 * child.getPaddingTop());
+ if (!isValidHolderAndChild(holder, child, items)) {
+ continue;
+ }
+
+ if (shouldDrawItemDivider(holder, items) && !hasDrawnPredictedAppDivider) {
+ // Draw the divider under the predicted app
+ parent.getBackground().getPadding(mTmpBounds);
+ int top = child.getTop() + child.getHeight();
+ c.drawLine(mTmpBounds.left, top, parent.getWidth() - mTmpBounds.right, top,
+ mPredictedAppsDividerPaint);
+ hasDrawnPredictedAppDivider = true;
+
+ } else if (grid.isPhone() && shouldDrawItemSection(holder, 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);
- if (!item.sectionName.equals(lastSectionName)) {
- lastSectionName = item.sectionName;
-
- // Find the section code points
- String sectionBegin = null;
- String sectionEnd = null;
- int charOffset = 0;
- while (charOffset < item.sectionName.length()) {
- int codePoint = item.sectionName.codePointAt(charOffset);
- int codePointSize = Character.charCount(codePoint);
- if (charOffset == 0) {
- // The first code point
- sectionBegin = item.sectionName.substring(charOffset, charOffset + codePointSize);
- } else if ((charOffset + codePointSize) >= item.sectionName.length()) {
- // The last code point
- sectionEnd = item.sectionName.substring(charOffset, charOffset + codePointSize);
- }
- charOffset += codePointSize;
+ 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;
+ }
+
- Point sectionBeginBounds = getAndCacheSectionBounds(sectionBegin);
- int minTop = cellTopOffset + sectionBeginBounds.y;
- int top = child.getTop() + cellTopOffset + sectionBeginBounds.y;
- int left = mIsRtl ? parent.getWidth() - mPaddingStart - mStartMargin :
+ // Find the section name bounds
+ PointF sectionBounds = getAndCacheSectionBounds(sectionName);
+
+ // Calculate where to draw the section
+ int sectionBaseline = (int) (viewTopOffset + sectionBounds.y);
+ int x = mIsRtl ? parent.getWidth() - mPaddingStart - mStartMargin :
mPaddingStart;
- int col = appIndexInSection % mAppsPerRow;
- int nextRowPos = Math.min(pos - col + mAppsPerRow, items.size() - 1);
- int alpha = 255;
- boolean fixedToRow = !items.get(nextRowPos).sectionName.equals(item.sectionName);
- if (fixedToRow) {
- alpha = Math.min(255, (int) (255 * (Math.max(0, top) / (float) minTop)));
- } else {
- // If we aren't fixed to the current row, then bound into the viewport
- top = Math.max(minTop, top);
+ 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);
}
- if (lastSectionHeight > 0 && top <= (lastSectionTop + lastSectionHeight)) {
- top += lastSectionTop - top + lastSectionHeight;
+
+ // 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);
}
- if (sectionEnd != null) {
- Point sectionEndBounds = getAndCacheSectionBounds(sectionEnd);
- c.drawText(sectionBegin + "/" + sectionEnd,
- left + (mStartMargin - sectionBeginBounds.x - sectionEndBounds.x) / 2, top,
- mSectionTextPaint);
- } else {
- c.drawText(sectionBegin, left + (mStartMargin - sectionBeginBounds.x) / 2, top,
- mSectionTextPaint);
- }
- lastSectionTop = top;
- lastSectionHeight = sectionBeginBounds.y + mSectionHeaderOffset;
+ c.drawText(sectionName, x, y, mSectionTextPaint);
+
+ lastSectionTop = y;
+ lastSectionHeight = (int) (sectionBounds.y + mSectionHeaderOffset);
+ lastSectionName = sectionName;
}
- }
- if (holder.mIsSectionHeader) {
- appIndexInSection = 0;
- } else {
- appIndexInSection++;
+ i += (sectionInfo.numApps - item.sectionAppIndex);
}
}
}
@@ -173,17 +181,23 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
// Do nothing
}
- private Point getAndCacheSectionBounds(String sectionName) {
- Point bounds = mCachedSectionBounds.get(sectionName);
+ /**
+ * 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 Point(mTmpBounds.width(), mTmpBounds.height());
+ bounds = new PointF(mSectionTextPaint.measureText(sectionName), mTmpBounds.height());
mCachedSectionBounds.put(sectionName, bounds);
}
return bounds;
}
- private boolean shouldDrawItemSection(ViewHolder holder, View child, int childIndex,
+ /**
+ * Returns whether we consider this a valid view holder for us to draw a divider or section for.
+ */
+ private boolean isValidHolderAndChild(ViewHolder holder, View child,
List<AlphabeticalAppsList.AdapterItem> items) {
// Ensure item is not already removed
GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
@@ -195,25 +209,44 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
if (holder == null) {
return false;
}
+ // Ensure we have a holder position
+ int pos = holder.getPosition();
+ if (pos < 0 || pos >= items.size()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns whether to draw the divider for a given child.
+ */
+ private boolean shouldDrawItemDivider(ViewHolder holder, List<AlphabeticalAppsList.AdapterItem> items) {
+ int pos = holder.getPosition();
+ return items.get(pos).isPredictedApp;
+ }
+
+ /**
+ * Returns whether to draw the section for the given child.
+ */
+ private boolean shouldDrawItemSection(ViewHolder holder, int childIndex,
+ List<AlphabeticalAppsList.AdapterItem> items) {
+ int pos = holder.getPosition();
+ AlphabeticalAppsList.AdapterItem item = items.get(pos);
+
// Ensure it's not an empty row
if (holder.mIsEmptyRow) {
return false;
}
- // Ensure we have a holder position
- int pos = holder.getPosition();
- if (pos < 0 || pos >= items.size()) {
+ // Ensure this is not a section break
+ if (item.isSectionHeader) {
return false;
}
- // Ensure this is not a section header
- if (items.get(pos).isSectionHeader) {
+ // Ensure this is not a predicted app
+ if (item.isPredictedApp) {
return false;
}
- // Only draw the header for the first item in a section, or whenever the sub-sections
- // changes (if AppsContainerView.GRID_MERGE_SECTIONS is true, but
- // AppsContainerView.GRID_MERGE_SECTION_HEADERS is false)
- return (childIndex == 0) ||
- items.get(pos - 1).isSectionHeader && !items.get(pos).isSectionHeader ||
- (!items.get(pos - 1).sectionName.equals(items.get(pos).sectionName));
+ // Draw the section header for the first item in each section
+ return (childIndex == 0) || (items.get(pos - 1).isSectionHeader && !item.isSectionHeader);
}
}
@@ -234,6 +267,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
@Thunk int mStartMargin;
@Thunk int mSectionHeaderOffset;
@Thunk Paint mSectionTextPaint;
+ @Thunk Paint mPredictedAppsDividerPaint;
public AppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow,
@@ -251,16 +285,20 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
mTouchListener = touchListener;
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
- 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);
- }
+ 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(
R.dimen.apps_view_section_text_size));
mSectionTextPaint.setColor(res.getColor(R.color.apps_view_section_text_color));
mSectionTextPaint.setAntiAlias(true);
+
+ mPredictedAppsDividerPaint = new Paint();
+ mPredictedAppsDividerPaint.setStrokeWidth(DynamicGrid.pxFromDp(1f, res.getDisplayMetrics()));
+ mPredictedAppsDividerPaint.setColor(0x1E000000);
+ mPredictedAppsDividerPaint.setAntiAlias(true);
}
/**
@@ -297,10 +335,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
*/
public RecyclerView.ItemDecoration getItemDecoration() {
// We don't draw any headers when we are uncomfortably dense
- if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS) {
- return mItemDecoration;
- }
- return null;
+ return mItemDecoration;
}
/**
@@ -315,10 +350,9 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
switch (viewType) {
case EMPTY_VIEW_TYPE:
return new ViewHolder(mLayoutInflater.inflate(R.layout.apps_empty_view, parent,
- false), false /* isSectionRow */, true /* isEmptyRow */);
+ false), true /* isEmptyRow */);
case SECTION_BREAK_VIEW_TYPE:
- return new ViewHolder(new View(parent.getContext()), true /* isSectionRow */,
- false /* isEmptyRow */);
+ return new ViewHolder(new View(parent.getContext()), false /* isEmptyRow */);
case ICON_VIEW_TYPE:
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
R.layout.apps_grid_row_icon_view, parent, false);
@@ -326,7 +360,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
icon.setOnClickListener(mIconClickListener);
icon.setOnLongClickListener(mIconLongClickListener);
icon.setFocusable(true);
- return new ViewHolder(icon, false /* isSectionRow */, false /* isEmptyRow */);
+ return new ViewHolder(icon, false /* isEmptyRow */);
default:
throw new RuntimeException("Unexpected view type");
}
diff --git a/src/com/android/launcher3/BaseContainerRecyclerView.java b/src/com/android/launcher3/BaseContainerRecyclerView.java
new file mode 100644
index 000000000..59e20ca2f
--- /dev/null
+++ b/src/com/android/launcher3/BaseContainerRecyclerView.java
@@ -0,0 +1,133 @@
+/*
+ * 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 {
+
+ /**
+ * Listener to get notified when the absolute scroll changes.
+ */
+ public interface OnScrollToListener {
+ void onScrolledTo(int x, int y);
+ }
+
+ private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
+
+ /** Keeps the last known scrolling delta/velocity along y-axis. */
+ @Thunk int mDy = 0;
+ @Thunk int mScrollY;
+ private float mDeltaThreshold;
+ private OnScrollToListener mScrollToListener;
+
+ public BaseContainerRecyclerView(Context context) {
+ this(context, null);
+ }
+
+ 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;
+ mScrollY += dy;
+ if (mScrollToListener != null) {
+ mScrollToListener.onScrolledTo(0, mScrollY);
+ }
+ }
+ }
+
+ /**
+ * Sets an additional scroll listener, only needed for LMR1 version of the support lib.
+ */
+ public void setOnScrollListenerProxy(OnScrollToListener listener) {
+ mScrollToListener = listener;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ 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
+ }
+
+ /**
+ * Updates the scroll position, used to workaround a RecyclerView issue with scrolling to
+ * position.
+ */
+ protected void updateScrollY(int scrollY) {
+ mScrollY = scrollY;
+ if (mScrollToListener != null) {
+ mScrollToListener.onScrolledTo(0, mScrollY);
+ }
+ }
+
+ /**
+ * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped.
+ */
+ protected boolean shouldStopScroll(MotionEvent ev) {
+ 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..bd1c625e3
--- /dev/null
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -0,0 +1,101 @@
+/*
+ * 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 = mInsets.bottom;
+ }
+ // 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.top += mFixedBoundsContainerInset;
+ mFixedBounds.bottom += 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/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index ae6ebba34..d32c91919 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -28,13 +28,13 @@ import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.util.TypedValue;
-import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.TextView;
import com.android.launcher3.IconCache.IconLoadRequest;
+import com.android.launcher3.widget.PackageItemInfo;
/**
* TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan
@@ -52,13 +52,14 @@ public class BubbleTextView extends TextView {
private static final int SHADOW_SMALL_COLOUR = 0xCC000000;
static final float PADDING_V = 3.0f;
+ private static final int DISPLAY_WORKSPACE = 0;
+ private static final int DISPLAY_ALL_APPS = 1;
+
private Drawable mIcon;
private final Drawable mBackground;
private final CheckLongPressHelper mLongPressHelper;
private final HolographicOutlineHelper mOutlineHelper;
- // TODO: Remove custom background handling code, as no instance of BubbleTextView use any
- // background.
private boolean mBackgroundSizeChanged;
private Bitmap mPressedBackground;
@@ -69,8 +70,6 @@ public class BubbleTextView extends TextView {
private final boolean mCustomShadowsEnabled;
private final boolean mLayoutHorizontal;
private final int mIconSize;
- private final int mIconPaddingSize;
- private final int mTextSize;
private int mTextColor;
private boolean mStayPressed;
@@ -95,14 +94,21 @@ public class BubbleTextView extends TextView {
R.styleable.BubbleTextView, defStyle, 0);
mCustomShadowsEnabled = a.getBoolean(R.styleable.BubbleTextView_customShadows, true);
mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
- mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride,
- grid.allAppsIconSizePx);
- mIconPaddingSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconPaddingOverride,
- grid.iconDrawablePaddingPx);
- mTextSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_textSizeOverride,
- grid.allAppsIconTextSizePx);
mDeferShadowGenerationOnTouch =
a.getBoolean(R.styleable.BubbleTextView_deferShadowGeneration, false);
+
+ int display = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
+ int defaultIconSize = grid.iconSizePx;
+ if (display == DISPLAY_WORKSPACE) {
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
+ } else if (display == DISPLAY_ALL_APPS) {
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
+ defaultIconSize = grid.allAppsIconSizePx;
+ }
+
+ mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride,
+ defaultIconSize);
+
a.recycle();
if (mCustomShadowsEnabled) {
@@ -113,11 +119,6 @@ public class BubbleTextView extends TextView {
mBackground = null;
}
- // If we are laying out horizontal, then center the text vertically
- if (mLayoutHorizontal) {
- setGravity(Gravity.CENTER_VERTICAL);
- }
-
mLongPressHelper = new CheckLongPressHelper(this);
mOutlineHelper = HolographicOutlineHelper.obtain(getContext());
@@ -128,26 +129,18 @@ public class BubbleTextView extends TextView {
setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate());
}
- public void onFinishInflate() {
- super.onFinishInflate();
-
- // Ensure we are using the right text size
- setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
- }
-
- public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache,
- boolean setDefaultPadding) {
- applyFromShortcutInfo(info, iconCache, setDefaultPadding, false);
+ public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache) {
+ applyFromShortcutInfo(info, iconCache, false);
}
public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache,
- boolean setDefaultPadding, boolean promiseStateChanged) {
+ boolean promiseStateChanged) {
Bitmap b = info.getIcon(iconCache);
FastBitmapDrawable iconDrawable = Utilities.createIconDrawable(b);
iconDrawable.setGhostModeEnabled(info.isDisabled != 0);
- setIcon(iconDrawable, mIconSize, setDefaultPadding ? mIconPaddingSize : -1);
+ setIcon(iconDrawable, mIconSize);
if (info.contentDescription != null) {
setContentDescription(info.contentDescription);
}
@@ -160,7 +153,7 @@ public class BubbleTextView extends TextView {
}
public void applyFromApplicationInfo(AppInfo info) {
- setIcon(Utilities.createIconDrawable(info.iconBitmap), mIconSize, mIconPaddingSize);
+ setIcon(Utilities.createIconDrawable(info.iconBitmap), mIconSize);
setText(info.title);
if (info.contentDescription != null) {
setContentDescription(info.contentDescription);
@@ -172,6 +165,20 @@ public class BubbleTextView extends TextView {
verifyHighRes();
}
+ public void applyFromPackageItemInfo(PackageItemInfo info) {
+ setIcon(Utilities.createIconDrawable(info.iconBitmap), mIconSize);
+ setText(info.title);
+ if (info.contentDescription != null) {
+ setContentDescription(info.contentDescription);
+ }
+ // We don't need to check the info since it's not a ShortcutInfo
+ super.setTag(info);
+
+ // Verify high res immediately
+ verifyHighRes();
+ }
+
+
@Override
protected boolean setFrame(int left, int top, int right, int bottom) {
if (getLeft() != left || getRight() != right || getTop() != top || getBottom() != bottom) {
@@ -409,7 +416,7 @@ public class BubbleTextView extends TextView {
preloadDrawable = (PreloadIconDrawable) mIcon;
} else {
preloadDrawable = new PreloadIconDrawable(mIcon, getPreloaderTheme());
- setIcon(preloadDrawable, mIconSize, -1);
+ setIcon(preloadDrawable, mIconSize);
}
preloadDrawable.setLevel(progressLevel);
@@ -437,7 +444,7 @@ public class BubbleTextView extends TextView {
/**
* Sets the icon for this view based on the layout direction.
*/
- private Drawable setIcon(Drawable icon, int iconSize, int drawablePadding) {
+ private Drawable setIcon(Drawable icon, int iconSize) {
mIcon = icon;
if (iconSize != -1) {
mIcon.setBounds(0, 0, iconSize, iconSize);
@@ -447,9 +454,6 @@ public class BubbleTextView extends TextView {
} else {
setCompoundDrawablesRelative(null, mIcon, null, null);
}
- if (drawablePadding != -1) {
- setCompoundDrawablePadding(drawablePadding);
- }
return icon;
}
@@ -463,7 +467,9 @@ public class BubbleTextView extends TextView {
applyFromApplicationInfo((AppInfo) info);
} else if (info instanceof ShortcutInfo) {
applyFromShortcutInfo((ShortcutInfo) info,
- LauncherAppState.getInstance().getIconCache(), false);
+ LauncherAppState.getInstance().getIconCache());
+ } else if (info instanceof PackageItemInfo) {
+ applyFromPackageItemInfo((PackageItemInfo) info);
}
}
}
@@ -488,6 +494,12 @@ public class BubbleTextView extends TextView {
mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
.updateIconInBackground(BubbleTextView.this, info);
}
+ } else if (getTag() instanceof PackageItemInfo) {
+ PackageItemInfo info = (PackageItemInfo) getTag();
+ if (info.usingLowResIcon) {
+ mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
+ .updateIconInBackground(BubbleTextView.this, info);
+ }
}
}
}
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 918517ebd..3bbf0e7d8 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -69,8 +69,10 @@ public class DeviceProfile {
String name;
float minWidthDps;
float minHeightDps;
- public float numRows;
- public float numColumns;
+ public int numRows;
+ public int numColumns;
+ public int numFolderRows;
+ public int numFolderColumns;
float numHotseatIcons;
float iconSize;
private float iconTextSize;
@@ -80,12 +82,11 @@ public class DeviceProfile {
int defaultLayoutId;
boolean isLandscape;
- boolean isTablet;
- boolean isLargeTablet;
+ public boolean isTablet;
+ public boolean isLargeTablet;
public boolean isLayoutRtl;
boolean transposeLayoutWithOrientation;
-
int desiredWorkspaceLeftRightMarginPx;
public int edgeMarginPx;
Rect defaultWidgetPadding;
@@ -138,8 +139,9 @@ public class DeviceProfile {
private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
- DeviceProfile(String n, float w, float h, float r, float c,
- float is, float its, float hs, float his, int dlId) {
+ DeviceProfile(String n, float w, float h,
+ int r, int c, int fr, int fc,
+ float is, float its, float hs, float his, int dlId) {
// Ensure that we have an odd number of hotseat items (since we need to place all apps)
if (hs % 2 == 0) {
throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
@@ -148,8 +150,12 @@ public class DeviceProfile {
name = n;
minWidthDps = w;
minHeightDps = h;
+
numRows = r;
numColumns = c;
+ numFolderRows = fr;
+ numFolderColumns = fc;
+
iconSize = is;
iconTextSize = its;
numHotseatIcons = hs;
@@ -210,6 +216,9 @@ public class DeviceProfile {
// Snap to the closest column count
numColumns = closestProfile.numColumns;
+ numFolderRows = closestProfile.numFolderRows;
+ numFolderColumns = closestProfile.numFolderColumns;
+
// Snap to the closest hotseat size
numHotseatIcons = closestProfile.numHotseatIcons;
hotseatAllAppsRank = (int) (numHotseatIcons / 2);
@@ -266,8 +275,8 @@ public class DeviceProfile {
DeviceProfile partnerDp = p.getDeviceProfileOverride(dm);
if (partnerDp != null) {
if (partnerDp.numRows > 0 && partnerDp.numColumns > 0) {
- numRows = partnerDp.numRows;
- numColumns = partnerDp.numColumns;
+ numRows = numFolderRows = partnerDp.numRows;
+ numColumns = numFolderColumns = partnerDp.numColumns;
}
if (partnerDp.allAppsShortEdgeCount > 0 && partnerDp.allAppsLongEdgeCount > 0) {
allAppsShortEdgeCount = partnerDp.allAppsShortEdgeCount;
@@ -428,13 +437,6 @@ 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/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index 91f97fa4a..2efdb06b9 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -38,7 +38,6 @@ import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
-import com.android.launcher3.InsettableFrameLayout.LayoutParams;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
@@ -153,6 +152,14 @@ public class DragLayer extends InsettableFrameLayout {
return false;
}
+ private boolean isEventOverDropTargetBar(MotionEvent ev) {
+ getDescendantRectRelativeToSelf(mLauncher.getSearchBar(), mHitRect);
+ if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
+ return true;
+ }
+ return false;
+ }
+
public void setBlockTouch(boolean block) {
mBlockTouches = block;
}
@@ -188,10 +195,16 @@ public class DragLayer extends InsettableFrameLayout {
}
}
- getDescendantRectRelativeToSelf(currentFolder, hitRect);
if (!isEventOverFolder(currentFolder, ev)) {
- mLauncher.closeFolder();
- return true;
+ if (isInAccessibleDrag()) {
+ // Do not close the folder if in drag and drop.
+ if (!isEventOverDropTargetBar(ev)) {
+ return true;
+ }
+ } else {
+ mLauncher.closeFolder();
+ return true;
+ }
}
}
return false;
@@ -228,11 +241,12 @@ public class DragLayer extends InsettableFrameLayout {
getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
if (accessibilityManager.isTouchExplorationEnabled()) {
final int action = ev.getAction();
- boolean isOverFolder;
+ boolean isOverFolderOrSearchBar;
switch (action) {
case MotionEvent.ACTION_HOVER_ENTER:
- isOverFolder = isEventOverFolder(currentFolder, ev);
- if (!isOverFolder) {
+ isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) ||
+ (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
+ if (!isOverFolderOrSearchBar) {
sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
mHoverPointClosesFolder = true;
return true;
@@ -240,12 +254,13 @@ public class DragLayer extends InsettableFrameLayout {
mHoverPointClosesFolder = false;
break;
case MotionEvent.ACTION_HOVER_MOVE:
- isOverFolder = isEventOverFolder(currentFolder, ev);
- if (!isOverFolder && !mHoverPointClosesFolder) {
+ isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) ||
+ (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
+ if (!isOverFolderOrSearchBar && !mHoverPointClosesFolder) {
sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
mHoverPointClosesFolder = true;
return true;
- } else if (!isOverFolder) {
+ } else if (!isOverFolderOrSearchBar) {
return true;
}
mHoverPointClosesFolder = false;
@@ -268,6 +283,12 @@ public class DragLayer extends InsettableFrameLayout {
}
}
+ private boolean isInAccessibleDrag() {
+ LauncherAccessibilityDelegate delegate = LauncherAppState
+ .getInstance().getAccessibilityDelegate();
+ return delegate != null && delegate.isInAccessibleDrag();
+ }
+
@Override
public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
@@ -275,6 +296,10 @@ public class DragLayer extends InsettableFrameLayout {
if (child == currentFolder) {
return super.onRequestSendAccessibilityEvent(child, event);
}
+
+ if (isInAccessibleDrag() && child instanceof SearchDropTargetBar) {
+ return super.onRequestSendAccessibilityEvent(child, event);
+ }
// Skip propagating onRequestSendAccessibilityEvent all for other children
// when a folder is open
return false;
@@ -288,6 +313,10 @@ public class DragLayer extends InsettableFrameLayout {
if (currentFolder != null) {
// Only add the folder as a child for accessibility when it is open
childrenForAccessibility.add(currentFolder);
+
+ if (isInAccessibleDrag()) {
+ childrenForAccessibility.add(mLauncher.getSearchBar());
+ }
} else {
super.addChildrenForAccessibility(childrenForAccessibility);
}
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
index a4b6704ac..3eec3d9ee 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/DragView.java
@@ -129,6 +129,10 @@ public class DragView extends View {
int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
measure(ms, ms);
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+
+ if (Utilities.isLmpOrAbove()) {
+ setElevation(getResources().getDimension(R.dimen.drag_elevation));
+ }
}
/** Sets the scale of the view over the normal workspace icon size. */
diff --git a/src/com/android/launcher3/DynamicGrid.java b/src/com/android/launcher3/DynamicGrid.java
index 24da97fc6..d22427f44 100644
--- a/src/com/android/launcher3/DynamicGrid.java
+++ b/src/com/android/launcher3/DynamicGrid.java
@@ -59,30 +59,30 @@ public class DynamicGrid {
DEFAULT_ICON_SIZE_PX = pxFromDp(DEFAULT_ICON_SIZE_DP, dm);
// Our phone profiles include the bar sizes in each orientation
deviceProfiles.add(new DeviceProfile("Super Short Stubby",
- 255, 300, 2, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4));
+ 255, 300, 2, 3, 2, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4));
deviceProfiles.add(new DeviceProfile("Shorter Stubby",
- 255, 400, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4));
+ 255, 400, 3, 3, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4));
deviceProfiles.add(new DeviceProfile("Short Stubby",
- 275, 420, 3, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
+ 275, 420, 3, 4, 3, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
deviceProfiles.add(new DeviceProfile("Stubby",
- 255, 450, 3, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
+ 255, 450, 3, 4, 3, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
deviceProfiles.add(new DeviceProfile("Nexus S",
- 296, 491.33f, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
+ 296, 491.33f, 4, 4, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
deviceProfiles.add(new DeviceProfile("Nexus 4",
- 335, 567, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
+ 335, 567, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
deviceProfiles.add(new DeviceProfile("Nexus 5",
- 359, 567, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
+ 359, 567, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
deviceProfiles.add(new DeviceProfile("Large Phone",
- 406, 694, 5, 5, 64, 14.4f, 5, 56, R.xml.default_workspace_5x5));
+ 406, 694, 5, 5, 4, 4, 64, 14.4f, 5, 56, R.xml.default_workspace_5x5));
// The tablet profile is odd in that the landscape orientation
// also includes the nav bar on the side
deviceProfiles.add(new DeviceProfile("Nexus 7",
- 575, 904, 5, 6, 72, 14.4f, 7, 60, R.xml.default_workspace_5x6));
+ 575, 904, 5, 6, 4, 5, 72, 14.4f, 7, 60, R.xml.default_workspace_5x6));
// Larger tablet profiles always have system bars on the top & bottom
deviceProfiles.add(new DeviceProfile("Nexus 10",
- 727, 1207, 5, 6, 76, 14.4f, 7, 64, R.xml.default_workspace_5x6));
+ 727, 1207, 5, 6, 4, 5, 76, 14.4f, 7, 64, R.xml.default_workspace_5x6));
deviceProfiles.add(new DeviceProfile("20-inch Tablet",
- 1527, 2527, 7, 7, 100, 20, 7, 72, R.xml.default_workspace_4x4));
+ 1527, 2527, 7, 7, 6, 6, 100, 20, 7, 72, R.xml.default_workspace_4x4));
mMinWidth = dpiFromPx(minWidthPx, dm);
mMinHeight = dpiFromPx(minHeightPx, dm);
mProfile = new DeviceProfile(context, deviceProfiles,
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index f2d7a6991..377e8eeff 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -27,6 +27,7 @@ import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.text.InputType;
import android.text.Selection;
@@ -182,6 +183,15 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
// name is complete, we have something to focus on, thus hiding the cursor and giving
// reliable behavior when clicking the text field (since it will always gain focus on click).
setFocusableInTouchMode(true);
+
+ if (Utilities.isLmpOrAbove()) {
+ int padding = getResources().getDimensionPixelSize(R.dimen.folder_shadow_padding);
+ setBackground(new InsetDrawable(
+ getResources().getDrawable(R.drawable.apps_list_bg),
+ padding, padding, padding, padding));
+ } else {
+ setBackgroundResource(R.drawable.quantum_panel);
+ }
}
@Override
@@ -274,6 +284,9 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
for (int i = 0; i < mContent.getChildCount(); i++) {
mContent.getPageAt(i).enableAccessibleDrag(enable, CellLayout.FOLDER_ACCESSIBILITY_DRAG);
}
+
+ mFooter.setImportantForAccessibility(enable ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS :
+ IMPORTANT_FOR_ACCESSIBILITY_AUTO);
mLauncher.getWorkspace().setAddNewPageOnDrag(!enable);
}
@@ -295,13 +308,14 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
mFolderName.setHint(sHintText);
// Convert to a string here to ensure that no other state associated with the text field
// gets saved.
- String newTitle = mFolderName.getText().toString();
+ CharSequence newTitle = mFolderName.getText();
mInfo.setTitle(newTitle);
LauncherModel.updateItemInDatabase(mLauncher, mInfo);
if (commit) {
sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
- String.format(getContext().getString(R.string.folder_renamed), newTitle));
+ String.format(getContext().getString(R.string.folder_renamed),
+ newTitle.toString()));
}
// In order to clear the focus from the text field, we set the focus on ourself. This
// ensures that every time the field is clicked, focus is gained, giving reliable behavior.
@@ -469,9 +483,15 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
PropertyValuesHolder tx = PropertyValuesHolder.ofFloat("translationX", transX, 0);
PropertyValuesHolder ty = PropertyValuesHolder.ofFloat("translationY", transY, 0);
+ Animator drift = LauncherAnimUtils.ofPropertyValuesHolder(this, tx, ty);
+ drift.setDuration(mMaterialExpandDuration);
+ drift.setStartDelay(mMaterialExpandStagger);
+ drift.setInterpolator(new LogDecelerateInterpolator(100, 0));
+
int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX());
int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
float radius = (float) Math.hypot(rx, ry);
+
AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
Animator reveal = LauncherAnimUtils.createCircularReveal(this, (int) getPivotX(),
(int) getPivotY(), 0, radius);
@@ -490,10 +510,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
textAlpha.setStartDelay(mMaterialExpandStagger);
textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
- Animator drift = LauncherAnimUtils.ofPropertyValuesHolder(this, tx, ty);
- drift.setDuration(mMaterialExpandDuration);
- drift.setStartDelay(mMaterialExpandStagger);
- drift.setInterpolator(new LogDecelerateInterpolator(60, 0));
anim.play(drift);
anim.play(iconsAlpha);
@@ -503,10 +519,12 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
openFolderAnim = anim;
mContentWrapper.setLayerType(LAYER_TYPE_HARDWARE, null);
+ mFooter.setLayerType(LAYER_TYPE_HARDWARE, null);
onCompleteRunnable = new Runnable() {
@Override
public void run() {
mContentWrapper.setLayerType(LAYER_TYPE_NONE, null);
+ mContentWrapper.setLayerType(LAYER_TYPE_NONE, null);
}
};
}
@@ -1095,8 +1113,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
// Move the item from the folder to the workspace, in the position of the folder
if (getItemCount() == 1) {
ShortcutInfo finalItem = mInfo.contents.get(0);
- child = mLauncher.createShortcut(R.layout.application, cellLayout,
- finalItem);
+ child = mLauncher.createShortcut(cellLayout, finalItem);
LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container,
mInfo.screenId, mInfo.cellX, mInfo.cellY);
}
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index f5836c295..b161b1cd3 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -710,7 +710,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
public void onTitleChanged(CharSequence title) {
- mFolderName.setText(title.toString());
+ mFolderName.setText(title);
setContentDescription(String.format(getContext().getString(R.string.folder_name_format),
title));
}
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index aea21c95b..930f91103 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -92,7 +92,7 @@ public class FolderInfo extends ItemInfo {
}
public void setTitle(CharSequence title) {
- this.title = title;
+ this.title = Utilities.trim(title);
for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).onTitleChanged(title);
}
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index f070a6bba..a6494d274 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -55,9 +55,6 @@ public class FolderPagedView extends PagedView {
private static final int[] sTempPosArray = new int[2];
- // TODO: Remove this restriction
- private static final int MAX_ITEMS_PER_PAGE = 4;
-
public final boolean rtlLayout;
private final LayoutInflater mInflater;
@@ -84,13 +81,8 @@ public class FolderPagedView extends PagedView {
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
- if (ALLOW_FOLDER_SCROLL) {
- mMaxCountX = Math.min((int) grid.numColumns, MAX_ITEMS_PER_PAGE);
- mMaxCountY = Math.min((int) grid.numRows, MAX_ITEMS_PER_PAGE);
- } else {
- mMaxCountX = (int) grid.numColumns;
- mMaxCountY = (int) grid.numRows;
- }
+ mMaxCountX = (int) grid.numFolderColumns;
+ mMaxCountY = (int) grid.numFolderRows;
mMaxItemsPerPage = mMaxCountX * mMaxCountY;
@@ -210,7 +202,7 @@ public class FolderPagedView extends PagedView {
public View createNewView(ShortcutInfo item) {
final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
R.layout.folder_application, null, false);
- textView.applyFromShortcutInfo(item, mIconCache, false);
+ textView.applyFromShortcutInfo(item, mIconCache);
textView.setOnClickListener(mFolder);
textView.setOnLongClickListener(mFolder);
textView.setOnFocusChangeListener(mFocusIndicatorView);
@@ -505,6 +497,10 @@ public class FolderPagedView extends PagedView {
}
}
+ public int getAllocatedContentSize() {
+ return mAllocatedContentSize;
+ }
+
/**
* Reorders the items such that the {@param empty} spot moves to {@param target}
*/
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 6c2aa397d..fff07c6ed 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -374,6 +374,9 @@ public class IconCache {
getTitleAndIcon(st,
st.promisedIntent != null ? st.promisedIntent : st.intent,
st.user, false);
+ } else if (info instanceof PackageItemInfo) {
+ PackageItemInfo pti = (PackageItemInfo) info;
+ getTitleAndIconForApp(pti.packageName, pti.user, false, pti);
}
mMainThreadExecutor.execute(new Runnable() {
@@ -400,7 +403,7 @@ public class IconCache {
UserHandleCompat user = info == null ? application.user : info.getUser();
CacheEntry entry = cacheLocked(application.componentName, info, user,
false, useLowResIcon);
- application.title = entry.title;
+ application.title = Utilities.trim(entry.title);
application.iconBitmap = getNonNullIcon(entry, user);
application.contentDescription = entry.contentDescription;
application.usingLowResIcon = entry.isLowResIcon;
@@ -413,7 +416,7 @@ public class IconCache {
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.title = Utilities.trim(entry.title);
application.iconBitmap = entry.icon;
application.contentDescription = entry.contentDescription;
application.usingLowResIcon = entry.isLowResIcon;
@@ -464,7 +467,7 @@ public class IconCache {
UserHandleCompat user, boolean usePkgIcon, boolean useLowResIcon) {
CacheEntry entry = cacheLocked(component, info, user, usePkgIcon, useLowResIcon);
shortcutInfo.setIcon(getNonNullIcon(entry, user));
- shortcutInfo.title = entry.title;
+ shortcutInfo.title = Utilities.trim(entry.title);
shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
shortcutInfo.usingLowResIcon = entry.isLowResIcon;
}
@@ -477,7 +480,7 @@ public class IconCache {
PackageItemInfo infoOut) {
CacheEntry entry = getEntryForPackageLocked(packageName, user, useLowResIcon);
infoOut.iconBitmap = getNonNullIcon(entry, user);
- infoOut.title = entry.title;
+ infoOut.title = Utilities.trim(entry.title);
infoOut.usingLowResIcon = entry.isLowResIcon;
infoOut.contentDescription = entry.contentDescription;
}
@@ -530,7 +533,7 @@ public class IconCache {
}
if (TextUtils.isEmpty(entry.title) && info != null) {
- entry.title = info.getLabel().toString();
+ entry.title = info.getLabel();
entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
}
}
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 23bcc8577..115598f7b 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -247,7 +247,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
try {
PackageManager pm = context.getPackageManager();
ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
- name = info.loadLabel(pm).toString();
+ name = info.loadLabel(pm);
} catch (PackageManager.NameNotFoundException nnfe) {
return "";
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1de383c65..8603a35df 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -64,6 +64,7 @@ import android.os.Handler;
import android.os.Message;
import android.os.StrictMode;
import android.os.SystemClock;
+import android.preference.PreferenceManager;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -347,6 +348,11 @@ public class Launcher extends Activity
private Canvas mFolderIconCanvas;
private Rect mRectForFolderAnimation = new Rect();
+ private DeviceProfile mDeviceProfile;
+
+ // This is set to the view that launched the activity that navigated the user away from
+ // launcher. Since there is no callback for when the activity has finished launching, enable
+ // the press state and keep this reference to reset the press state when we return to launcher.
private BubbleTextView mWaitingForResume;
protected static HashMap<String, CustomAppWidget> sCustomAppWidgets =
@@ -423,7 +429,7 @@ public class Launcher extends Activity
LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
// Lazy-initialize the dynamic grid
- DeviceProfile grid = app.initDynamicGrid(this);
+ mDeviceProfile = app.initDynamicGrid(this);
// the LauncherApplication should call this, but in case of Instrumentation it might not be present yet
mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
@@ -431,7 +437,7 @@ public class Launcher extends Activity
mIsSafeModeEnabled = getPackageManager().isSafeMode();
mModel = app.setLauncher(this);
mIconCache = app.getIconCache();
- mIconCache.flushInvalidIcons(grid);
+ mIconCache.flushInvalidIcons(mDeviceProfile);
mDragController = new DragController(this);
mInflater = getLayoutInflater();
mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
@@ -457,7 +463,7 @@ public class Launcher extends Activity
setContentView(R.layout.launcher);
setupViews();
- grid.layout(this);
+ mDeviceProfile.layout(this);
registerContentObservers();
@@ -528,7 +534,8 @@ 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
@@ -1017,16 +1024,24 @@ public class Launcher extends Activity
if (mOnResumeState == State.WORKSPACE) {
showWorkspace(false);
} else if (mOnResumeState == State.APPS) {
- showAppsView(false /* animated */, false /* resetListToTop */);
+ boolean launchedFromApp = (mWaitingForResume != null);
+ // Don't update the predicted apps if the user is returning to launcher in the apps
+ // view after launching an app, as they may be depending on the UI to be static to
+ // switch to another app, otherwise, if it was
+ showAppsView(false /* animated */, false /* resetListToTop */,
+ !launchedFromApp /* updatePredictedApps */);
} else if (mOnResumeState == State.WIDGETS) {
showWidgetsView(false, false);
}
mOnResumeState = State.NONE;
// Restore the apps state if we are in all apps
- if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mState == State.APPS) {
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onAllAppsShown();
+ if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
+ // Otherwise, notify the callbacks if we are in all apps mode
+ if (mState == State.APPS) {
+ if (mLauncherCallbacks != null) {
+ mLauncherCallbacks.onAllAppsShown();
+ }
}
}
@@ -1362,7 +1377,9 @@ public class Launcher extends Activity
mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
AppWidgetProviderInfo info = savedState.getParcelable(
RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
- mPendingAddWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(this, info);
+ mPendingAddWidgetInfo = info == null ?
+ null : LauncherAppWidgetProviderInfo.fromProviderInfo(this, info);
+
mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
setWaitingForResult(true);
mRestoring = true;
@@ -1510,22 +1527,22 @@ public class Launcher extends Activity
* @return A View inflated from R.layout.application.
*/
View createShortcut(ShortcutInfo info) {
- return createShortcut(R.layout.application,
- (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
+ return createShortcut((ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
}
/**
* Creates a view representing a shortcut inflated from the specified resource.
*
- * @param layoutResId The id of the XML layout used to create the shortcut.
* @param parent The group the shortcut belongs to.
* @param info The data structure describing the shortcut.
*
* @return A View inflated from layoutResId.
*/
- public View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
- BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
- favorite.applyFromShortcutInfo(info, mIconCache, true);
+ public View createShortcut(ViewGroup parent, ShortcutInfo info) {
+ BubbleTextView favorite = (BubbleTextView) mInflater.inflate(R.layout.application,
+ parent, false);
+ favorite.applyFromShortcutInfo(info, mIconCache);
+ favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
favorite.setOnClickListener(this);
favorite.setOnFocusChangeListener(mFocusHandler);
return favorite;
@@ -2618,7 +2635,9 @@ public class Launcher extends Activity
if (isAppsViewVisible()) {
showWorkspace(true);
} else {
- showAppsView(true /* animated */, false /* resetListToTop */);
+ // Try and refresh the set of predicted apps before we enter launcher
+ showAppsView(true /* animated */, false /* resetListToTop */,
+ true /* updatePredictedApps */);
}
}
@@ -3396,10 +3415,13 @@ public class Launcher extends Activity
/**
* Shows the apps view.
*/
- void showAppsView(boolean animated, boolean resetListToTop) {
+ void showAppsView(boolean animated, boolean resetListToTop, boolean updatePredictedApps) {
if (resetListToTop) {
mAppsView.scrollToTop();
}
+ if (updatePredictedApps) {
+ tryAndUpdatePredictedApps();
+ }
showAppsOrWidgets(animated, State.APPS);
}
@@ -3508,12 +3530,26 @@ public class Launcher extends Activity
void exitSpringLoadedDragMode() {
if (mState == State.APPS_SPRING_LOADED) {
- showAppsView(true, false);
+ showAppsView(true /* animated */, false /* resetListToTop */,
+ false /* updatePredictedApps */);
} else if (mState == State.WIDGETS_SPRING_LOADED) {
showWidgetsView(true, false);
}
}
+ /**
+ * Updates the set of predicted apps if it hasn't been updated since the last time Launcher was
+ * resumed.
+ */
+ private void tryAndUpdatePredictedApps() {
+ if (mLauncherCallbacks != null) {
+ List<ComponentName> apps = mLauncherCallbacks.getPredictedApps();
+ if (!apps.isEmpty()) {
+ mAppsView.setPredictedApps(apps);
+ }
+ }
+ }
+
void lockAllApps() {
// TODO
}
@@ -4156,13 +4192,11 @@ public class Launcher extends Activity
}
public boolean useVerticalBarLayout() {
- return LauncherAppState.getInstance().getDynamicGrid().
- getDeviceProfile().isVerticalBarLayout();
+ return mDeviceProfile.isVerticalBarLayout();
}
protected Rect getSearchBarBounds() {
- return LauncherAppState.getInstance().getDynamicGrid().
- getDeviceProfile().getSearchBarBounds();
+ return mDeviceProfile.getSearchBarBounds();
}
public void bindSearchablesChanged() {
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/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index bb4580ce7..af680f247 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -66,7 +66,7 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo {
public String getLabel(PackageManager packageManager) {
if (isCustomWidget) {
- return label.toString().trim();
+ return Utilities.trim(label);
}
return super.loadLabel(packageManager);
}
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 25c86c977..0124d1f28 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -11,6 +11,7 @@ import android.view.ViewGroup;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
/**
* LauncherCallbacks is an interface used to extend the Launcher activity. It includes many hooks
@@ -90,6 +91,7 @@ public interface LauncherCallbacks {
public boolean overrideWallpaperDimensions();
public boolean isLauncherPreinstalled();
public boolean overrideAllAppsSearch();
+ public List<ComponentName> getPredictedApps();
/**
* Returning true will immediately result in a call to {@link #setLauncherOverlayView(ViewGroup,
diff --git a/src/com/android/launcher3/LauncherExtension.java b/src/com/android/launcher3/LauncherExtension.java
index 8174af045..14ad6016c 100644
--- a/src/com/android/launcher3/LauncherExtension.java
+++ b/src/com/android/launcher3/LauncherExtension.java
@@ -15,6 +15,7 @@ import android.view.ViewGroup;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
/**
* This class represents a very trivial LauncherExtension. It primarily serves as a simple
@@ -259,6 +260,11 @@ public class LauncherExtension extends Launcher {
}
@Override
+ public List<ComponentName> getPredictedApps() {
+ return new ArrayList<>();
+ }
+
+ @Override
public boolean isLauncherPreinstalled() {
return false;
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 7efdf3284..658a3e287 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -969,7 +969,7 @@ public class LauncherModel extends BroadcastReceiver
break;
}
- folderInfo.title = c.getString(titleIndex);
+ folderInfo.title = Utilities.trim(c.getString(titleIndex));
folderInfo.id = id;
folderInfo.container = c.getInt(containerIndex);
folderInfo.screenId = c.getInt(screenIndex);
@@ -2144,7 +2144,7 @@ public class LauncherModel extends BroadcastReceiver
id = c.getLong(idIndex);
FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
- folderInfo.title = c.getString(titleIndex);
+ folderInfo.title = Utilities.trim(c.getString(titleIndex));
folderInfo.id = id;
container = c.getInt(containerIndex);
folderInfo.container = container;
@@ -2331,6 +2331,22 @@ public class LauncherModel extends BroadcastReceiver
return;
}
+ // Remove any empty folder
+ LongArrayMap<FolderInfo> emptyFolders = sBgFolders.clone();
+ for (ItemInfo item: sBgItemsIdMap) {
+ long container = item.container;
+ if (emptyFolders.containsKey(container)) {
+ emptyFolders.remove(container);
+ }
+ }
+ for (FolderInfo folder : emptyFolders) {
+ long folderId = folder.id;
+ sBgFolders.remove(folderId);
+ sBgItemsIdMap.remove(folderId);
+ sBgWorkspaceItems.remove(folder);
+ itemsToRemove.add(folderId);
+ }
+
if (itemsToRemove.size() > 0) {
ContentProviderClient client = contentResolver.acquireContentProviderClient(
contentUri);
@@ -2810,6 +2826,8 @@ public class LauncherModel extends BroadcastReceiver
} else {
mHandler.post(r);
}
+ loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks),
+ false /* refresh */);
}
private void loadAllApps() {
@@ -2871,8 +2889,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");
@@ -2885,6 +2901,8 @@ 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");
@@ -3197,7 +3215,7 @@ public class LauncherModel extends BroadcastReceiver
if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
&& si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
si.updateIcon(mIconCache);
- si.title = appInfo.title.toString();
+ si.title = Utilities.trim(appInfo.title);
si.contentDescription = appInfo.contentDescription;
infoUpdated = true;
}
@@ -3426,18 +3444,17 @@ public class LauncherModel extends BroadcastReceiver
if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
String title = (cursor != null) ? cursor.getString(titleIndex) : null;
if (!TextUtils.isEmpty(title)) {
- info.title = title;
+ info.title = Utilities.trim(title);
}
} else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
if (TextUtils.isEmpty(info.title)) {
- info.title = (cursor != null) ? cursor.getString(titleIndex) : "";
+ info.title = (cursor != null) ? Utilities.trim(cursor.getString(titleIndex)) : "";
}
} else {
throw new InvalidParameterException("Invalid restoreType " + promiseType);
}
- info.contentDescription = mUserManager.getBadgedLabelForUser(
- info.title.toString(), info.user);
+ info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
info.promisedIntent = intent;
info.status = promiseType;
@@ -3499,7 +3516,7 @@ public class LauncherModel extends BroadcastReceiver
// from the db
if (TextUtils.isEmpty(info.title) && c != null) {
- info.title = c.getString(titleIndex);
+ info.title = Utilities.trim(c.getString(titleIndex));
}
// fall back to the class name of the activity
@@ -3509,8 +3526,7 @@ public class LauncherModel extends BroadcastReceiver
info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
info.user = user;
- info.contentDescription = mUserManager.getBadgedLabelForUser(
- info.title.toString(), info.user);
+ info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
if (lai != null) {
info.flags = AppInfo.initFlags(lai);
}
@@ -3576,7 +3592,7 @@ public class LauncherModel extends BroadcastReceiver
// TODO: If there's an explicit component and we can't install that, delete it.
- info.title = c.getString(titleIndex);
+ info.title = Utilities.trim(c.getString(titleIndex));
int iconType = c.getInt(iconTypeIndex);
switch (iconType) {
@@ -3654,9 +3670,8 @@ public class LauncherModel extends BroadcastReceiver
}
info.setIcon(icon);
- info.title = name;
- info.contentDescription = mUserManager.getBadgedLabelForUser(
- info.title.toString(), info.user);
+ info.title = Utilities.trim(name);
+ info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
info.intent = intent;
info.customIcon = customIcon;
info.iconResource = iconResource;
@@ -3697,16 +3712,16 @@ public class LauncherModel extends BroadcastReceiver
labelA = mLabelCache.get(a);
} else {
labelA = (a instanceof LauncherAppWidgetProviderInfo)
- ? mManager.loadLabel((LauncherAppWidgetProviderInfo) a)
- : ((ResolveInfo) a).loadLabel(mPackageManager).toString().trim();
+ ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) a))
+ : Utilities.trim(((ResolveInfo) a).loadLabel(mPackageManager));
mLabelCache.put(a, labelA);
}
if (mLabelCache.containsKey(b)) {
labelB = mLabelCache.get(b);
} else {
labelB = (b instanceof LauncherAppWidgetProviderInfo)
- ? mManager.loadLabel((LauncherAppWidgetProviderInfo) b)
- : ((ResolveInfo) b).loadLabel(mPackageManager).toString().trim();
+ ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) b))
+ : Utilities.trim(((ResolveInfo) b).loadLabel(mPackageManager));
mLabelCache.put(b, labelB);
}
return mCollator.compare(labelA, labelB);
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 51f84bfcd..73ae51c3e 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -378,11 +378,12 @@ public class LauncherStateTransitionAnimation {
dispatchOnLauncherTransitionStart(toView, animated, false);
// Enable all necessary layers
+ boolean isLmpOrAbove = Utilities.isLmpOrAbove();
for (View v : layerViews.keySet()) {
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
- if (Utilities.isViewAttachedToWindow(v)) {
+ if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
v.buildLayer();
}
}
@@ -697,11 +698,12 @@ public class LauncherStateTransitionAnimation {
dispatchOnLauncherTransitionStart(toView, animated, false);
// Enable all necessary layers
+ boolean isLmpOrAbove = Utilities.isLmpOrAbove();
for (View v : layerViews.keySet()) {
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
- if (Utilities.isLmpOrAbove()) {
+ if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
v.buildLayer();
}
}
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 6354fcd28..8be48721c 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -153,7 +153,7 @@ public class ShortcutInfo extends ItemInfo {
Bitmap icon, UserHandleCompat user) {
this();
this.intent = intent;
- this.title = title;
+ this.title = Utilities.trim(title);
this.contentDescription = contentDescription;
mIcon = icon;
this.user = user;
@@ -161,7 +161,7 @@ public class ShortcutInfo extends ItemInfo {
public ShortcutInfo(Context context, ShortcutInfo info) {
super(info);
- title = info.title.toString();
+ title = Utilities.trim(info.title);
intent = new Intent(info.intent);
if (info.iconResource != null) {
iconResource = new Intent.ShortcutIconResource();
@@ -179,7 +179,7 @@ public class ShortcutInfo extends ItemInfo {
/** TODO: Remove this. It's only called by ApplicationInfo.makeShortcut. */
public ShortcutInfo(AppInfo info) {
super(info);
- title = info.title.toString();
+ title = Utilities.trim(info.title);
intent = new Intent(info.intent);
customIcon = false;
flags = info.flags;
@@ -281,7 +281,7 @@ public class ShortcutInfo extends ItemInfo {
public static ShortcutInfo fromActivityInfo(LauncherActivityInfoCompat info, Context context) {
final ShortcutInfo shortcut = new ShortcutInfo();
shortcut.user = info.getUser();
- shortcut.title = info.getLabel().toString();
+ shortcut.title = Utilities.trim(info.getLabel());
shortcut.contentDescription = UserManagerCompat.getInstance(context)
.getBadgedLabelForUser(info.getLabel(), info.getUser());
shortcut.customIcon = false;
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 2dbf078a4..298174768 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -54,6 +54,8 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Various utilities shared amongst the Launcher's classes.
@@ -67,6 +69,9 @@ public final class Utilities {
private static final Rect sOldBounds = new Rect();
private static final Canvas sCanvas = new Canvas();
+ private static final Pattern sTrimPattern =
+ Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
+
static {
sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
Paint.FILTER_BITMAP_FLAG));
@@ -616,4 +621,14 @@ public final class Utilities {
return false;
}
+
+ /**
+ * Trims the string, removing all whitespace at the beginning and end of the string.
+ * Non-breaking whitespaces are also removed.
+ */
+ public static String trim(CharSequence s) {
+ // Just strip any sequence of whitespace or java space characters from the beginning and end
+ Matcher m = sTrimPattern.matcher(s);
+ return m.replaceAll("$1");
+ }
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 5c2121ab8..4004b1d90 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -3558,8 +3558,7 @@ public class Workspace extends SmoothPagedView
// Came from all apps -- make a copy
info = ((AppInfo) info).makeShortcut();
}
- view = mLauncher.createShortcut(R.layout.application, cellLayout,
- (ShortcutInfo) info);
+ view = mLauncher.createShortcut(cellLayout, (ShortcutInfo) info);
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
@@ -4185,7 +4184,7 @@ public class Workspace extends SmoothPagedView
&& packageNames.contains(cn.getPackageName())) {
shortcutInfo.isDisabled |= reason;
BubbleTextView shortcut = (BubbleTextView) v;
- shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, true, false);
+ shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache);
if (parent != null) {
parent.invalidate();
@@ -4371,7 +4370,7 @@ public class Workspace extends SmoothPagedView
BubbleTextView shortcut = (BubbleTextView) v;
boolean oldPromiseState = getTextViewIcon(shortcut)
instanceof PreloadIconDrawable;
- shortcut.applyFromShortcutInfo(si, mIconCache, true,
+ shortcut.applyFromShortcutInfo(si, mIconCache,
si.isPromise() != oldPromiseState);
if (parent != null) {
diff --git a/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
index fc105b4a4..ff9989036 100644
--- a/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
@@ -24,23 +24,29 @@ import com.android.launcher3.R;
* Implementation of {@link DragAndDropAccessibilityDelegate} to support DnD in a folder.
*/
public class FolderAccessibilityHelper extends DragAndDropAccessibilityDelegate {
+
+ /**
+ * 0-index position for the first cell in {@link #mView} in {@link #mParent}.
+ */
private final int mStartPosition;
+ private final FolderPagedView mParent;
+
public FolderAccessibilityHelper(CellLayout layout) {
super(layout);
- FolderPagedView parent = (FolderPagedView) layout.getParent();
+ mParent = (FolderPagedView) layout.getParent();
- int index = parent.indexOfChild(layout);
- mStartPosition = 1 + index * layout.getCountX() * layout.getCountY();
+ int index = mParent.indexOfChild(layout);
+ mStartPosition = index * layout.getCountX() * layout.getCountY();
}
@Override
protected int intersectsValidDropTarget(int id) {
- return id;
+ return Math.min(id, mParent.getAllocatedContentSize() - mStartPosition - 1);
}
@Override
protected String getLocationDescriptionForIconDrop(int id) {
- return mContext.getString(R.string.move_to_position, id + mStartPosition);
+ return mContext.getString(R.string.move_to_position, id + mStartPosition + 1);
}
@Override
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index 42e9e3c58..6f89d0eb0 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -145,7 +145,7 @@ public class WorkspaceAccessibilityHelper extends DragAndDropAccessibilityDelega
if (info instanceof ShortcutInfo) {
return mContext.getString(R.string.create_folder_with, info.title);
} else if (info instanceof FolderInfo) {
- if (TextUtils.isEmpty(info.title.toString().trim())) {
+ if (TextUtils.isEmpty(info.title)) {
// Find the first item in the folder.
FolderInfo folder = (FolderInfo) info;
ShortcutInfo firstItem = null;
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index f890706ff..ec1fb669f 100644
--- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -1,6 +1,7 @@
package com.android.launcher3.compat;
import android.content.Context;
+import com.android.launcher3.Utilities;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
@@ -51,12 +52,15 @@ class BaseAlphabeticIndex {
*/
public class AlphabeticIndexCompat extends BaseAlphabeticIndex {
+ private static final String MID_DOT = "\u2219";
+
private Object mAlphabeticIndex;
private Method mAddLabelsMethod;
private Method mSetMaxLabelCountMethod;
private Method mGetBucketIndexMethod;
private Method mGetBucketLabelMethod;
private boolean mHasValidAlphabeticIndex;
+ private String mDefaultMiscLabel;
public AlphabeticIndexCompat(Context context) {
super();
@@ -71,12 +75,20 @@ public class AlphabeticIndexCompat extends BaseAlphabeticIndex {
mAlphabeticIndex = ctor.newInstance(curLocale);
try {
// Ensure we always have some base English locale buckets
- if (!curLocale.getLanguage().equals(new Locale("en").getLanguage())) {
+ if (!curLocale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
mAddLabelsMethod.invoke(mAlphabeticIndex, Locale.ENGLISH);
}
} catch (Exception e) {
e.printStackTrace();
}
+ if (curLocale.getLanguage().equals(Locale.JAPANESE.getLanguage())) {
+ // Japanese character 他 ("misc")
+ mDefaultMiscLabel = "\u4ed6";
+ // TODO(winsonc, omakoto): We need to handle Japanese sections better, especially the kanji
+ } else {
+ // Dot
+ mDefaultMiscLabel = MID_DOT;
+ }
mHasValidAlphabeticIndex = true;
} catch (Exception e) {
mHasValidAlphabeticIndex = false;
@@ -102,16 +114,25 @@ public class AlphabeticIndexCompat extends BaseAlphabeticIndex {
/**
* Computes the section name for an given string {@param s}.
*/
- public String computeSectionName(String s) {
+ public String computeSectionName(CharSequence cs) {
+ String s = Utilities.trim(cs);
String sectionName = getBucketLabel(getBucketIndex(s));
- if (sectionName.trim().isEmpty() && s.length() > 0) {
- boolean startsWithDigit = Character.isDigit(Character.codePointAt(s.trim(), 0));
+ if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) {
+ int c = s.codePointAt(0);
+ boolean startsWithDigit = Character.isDigit(c);
if (startsWithDigit) {
// Digit section
return "#";
} else {
- // Unknown section
- return "\u2022";
+ boolean startsWithLetter = Character.isLetter(c);
+ if (startsWithLetter) {
+ return mDefaultMiscLabel;
+ } else {
+ // In languages where these differ, this ensures that we differentiate
+ // between the misc section in the native language and a misc section
+ // for everything else.
+ return MID_DOT;
+ }
}
}
return sectionName;
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
index 767f16f62..967b53b0b 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
@@ -46,7 +46,7 @@ class AppWidgetManagerCompatV16 extends AppWidgetManagerCompat {
@Override
public String loadLabel(LauncherAppWidgetProviderInfo info) {
- return info.label.trim();
+ return Utilities.trim(info.label);
}
@Override
diff --git a/src/com/android/launcher3/widget/PackageItemInfo.java b/src/com/android/launcher3/widget/PackageItemInfo.java
index 1a1de55c2..8f45a7754 100644
--- a/src/com/android/launcher3/widget/PackageItemInfo.java
+++ b/src/com/android/launcher3/widget/PackageItemInfo.java
@@ -49,7 +49,7 @@ public class PackageItemInfo extends ItemInfo {
@Override
public String toString() {
- return "PackageItemInfo(title=" + title.toString() + " id=" + this.id
+ return "PackageItemInfo(title=" + title + " id=" + this.id
+ " type=" + this.itemType + " container=" + this.container
+ " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY
+ " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 2df170eff..7ca4df979 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -49,7 +49,15 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
private static final boolean DEBUG = false;
private static final int FADE_IN_DURATION_MS = 90;
+
+ /** Widget cell width is calculated by multiplying this factor to grid cell width. */
+ private static final float WIDTH_SCALE = 2.6f;
+
+ /** Widget preview width is calculated by multiplying this factor to the widget cell width. */
+ private static final float PREVIEW_SCALE = 0.8f;
+
private int mPresetPreviewSize;
+ int cellSize;
private ImageView mWidgetImage;
private TextView mWidgetName;
@@ -76,12 +84,17 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
final Resources r = context.getResources();
mDimensionsFormatString = r.getString(R.string.widget_dims_format);
- mPresetPreviewSize = r.getDimensionPixelSize(R.dimen.widget_preview_size);
+ setContainerWidth();
setWillNotDraw(false);
setClipToPadding(false);
setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate());
+ }
+ private void setContainerWidth() {
+ DeviceProfile profile = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
+ cellSize = (int) (profile.cellWidthPx * WIDTH_SCALE);
+ mPresetPreviewSize = (int) (profile.cellWidthPx * WIDTH_SCALE * PREVIEW_SCALE);
}
@Override
@@ -222,10 +235,9 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
* Helper method to get the string info of the tag.
*/
private String getTagToString() {
- if (getTag() instanceof PendingAddWidgetInfo) {
- return ((PendingAddWidgetInfo)getTag()).toString();
- } else if (getTag() instanceof PendingAddShortcutInfo) {
- return ((PendingAddShortcutInfo)getTag()).toString();
+ if (getTag() instanceof PendingAddWidgetInfo ||
+ getTag() instanceof PendingAddShortcutInfo) {
+ return getTag().toString();
}
return "";
}
diff --git a/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java b/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java
index 65694bfaa..6f15324c1 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java
@@ -17,26 +17,13 @@
package com.android.launcher3.widget;
import android.content.Context;
-import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
-import android.view.MotionEvent;
-
-import com.android.launcher3.util.Thunk;
+import com.android.launcher3.BaseContainerRecyclerView;
/**
* The widgets recycler view container.
- * <p>
- * Overwritten to NOT intercept a touch sequence that started when the {@link RecycleView}
- * scrolling slowing down below the internally defined threshold.
*/
-public class WidgetsContainerRecyclerView extends RecyclerView
- implements RecyclerView.OnItemTouchListener {
-
- private static final int SCROLL_DELTA_THRESHOLD = 4;
-
- /** Keeps the last known scrolling delta/velocity along y-axis. */
- @Thunk int mDy = 0;
- private float mDeltaThreshold;
+public class WidgetsContainerRecyclerView extends BaseContainerRecyclerView {
public WidgetsContainerRecyclerView(Context context) {
this(context, null);
@@ -48,47 +35,6 @@ public class WidgetsContainerRecyclerView extends RecyclerView
public WidgetsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD;
-
- ScrollListener listener = new ScrollListener();
- setOnScrollListener(listener);
- }
-
- private class ScrollListener extends RecyclerView.OnScrollListener {
- public ScrollListener() {
- }
-
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- mDy = dy;
- }
}
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- addOnItemTouchListener(this);
- }
-
- @Override
- public boolean onInterceptTouchEvent(RecyclerView rv, 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.
- 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
- }
} \ 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..f8d7d9256 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,
+ mFixedBounds.bottom);
+ }
}
/**
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index a7728a11b..d6e062874 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -17,21 +17,27 @@ package com.android.launcher3.widget;
import android.content.Context;
import android.content.pm.ResolveInfo;
+import android.support.v7.widget.RecyclerView;
+import android.content.res.Resources;
import android.support.v7.widget.RecyclerView.Adapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.LinearLayout;
+import com.android.launcher3.BubbleTextView;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DynamicGrid;
import com.android.launcher3.IconCache;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.R;
import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.compat.UserHandleCompat;
import java.util.List;
@@ -46,7 +52,7 @@ import java.util.List;
public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
private static final String TAG = "WidgetsListAdapter";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private Context mContext;
private Launcher mLauncher;
@@ -59,6 +65,9 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
private View.OnClickListener mIconClickListener;
private View.OnLongClickListener mIconLongClickListener;
+ private static final int PRESET_INDENT_SIZE_TABLET = 56;
+ private int mIndent = 0;
+
public WidgetsListAdapter(Context context,
View.OnClickListener iconClickListener,
View.OnLongClickListener iconLongClickListener,
@@ -71,6 +80,8 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
mLauncher = launcher;
mIconCache = LauncherAppState.getInstance().getIconCache();
+
+ setContainerHeight();
}
public void setWidgetsModel(WidgetsModel w) {
@@ -96,6 +107,7 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
// Add more views.
// if there are too many, hide them.
int diff = infoList.size() - row.getChildCount();
+
if (diff > 0) {
for (int i = 0; i < diff; i++) {
WidgetCell widget = new WidgetCell(mContext);
@@ -105,6 +117,11 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
// set up touch.
widget.setOnClickListener(mIconClickListener);
widget.setOnLongClickListener(mIconLongClickListener);
+ LayoutParams lp = widget.getLayoutParams();
+ lp.height = widget.cellSize;
+ lp.width = widget.cellSize;
+ widget.setLayoutParams(lp);
+
row.addView(widget);
}
} else if (diff < 0) {
@@ -115,16 +132,8 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
// Bind the views in the application info section.
PackageItemInfo infoOut = mWidgetsModel.getPackageItemInfo(pos);
- if (infoOut.usingLowResIcon) {
- // TODO(hyunyoungs): call this in none UI thread in the same way as BubbleTextView.
- mIconCache.getTitleAndIconForApp(infoOut.packageName,
- UserHandleCompat.myUserHandle(), false /* useLowResIcon */, infoOut);
- }
-
- TextView tv = ((TextView) holder.getContent().findViewById(R.id.section));
- tv.setText(infoOut.title);
- ImageView iv = (ImageView) holder.getContent().findViewById(R.id.section_image);
- iv.setImageBitmap(infoOut.iconBitmap);
+ BubbleTextView tv = ((BubbleTextView) holder.getContent().findViewById(R.id.section));
+ tv.applyFromPackageItemInfo(infoOut);
// Bind the view in the widget horizontal tray region.
for (int i=0; i < infoList.size(); i++) {
@@ -156,6 +165,10 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
ViewGroup container = (ViewGroup) mLayoutInflater.inflate(
R.layout.widgets_list_row_view, parent, false);
+ LinearLayout cellList = (LinearLayout) container.findViewById(R.id.widgets_cell_list);
+ MarginLayoutParams lp = (MarginLayoutParams) cellList.getLayoutParams();
+ lp.setMarginStart(mIndent);
+ cellList.setLayoutParams(lp);
return new WidgetsRowViewHolder(container);
}
@@ -180,4 +193,12 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
}
return mWidgetPreviewLoader;
}
+
+ private void setContainerHeight() {
+ Resources r = mContext.getResources();
+ DeviceProfile profile = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
+ if (profile.isLargeTablet || profile.isTablet) {
+ mIndent = DynamicGrid.pxFromDp(PRESET_INDENT_SIZE_TABLET, r.getDisplayMetrics());
+ }
+ }
}