summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/launcher3/AppWidgetsRestoredReceiver.java22
-rw-r--r--src/com/android/launcher3/CellLayout.java8
-rw-r--r--src/com/android/launcher3/DeviceProfile.java75
-rw-r--r--src/com/android/launcher3/FolderInfo.java6
-rw-r--r--src/com/android/launcher3/IconCache.java2
-rw-r--r--src/com/android/launcher3/ItemInfo.java13
-rw-r--r--src/com/android/launcher3/Launcher.java27
-rw-r--r--src/com/android/launcher3/LauncherProvider.java82
-rw-r--r--src/com/android/launcher3/LauncherStateTransitionAnimation.java4
-rw-r--r--src/com/android/launcher3/SettingsActivity.java84
-rw-r--r--src/com/android/launcher3/Utilities.java24
-rw-r--r--src/com/android/launcher3/Workspace.java32
-rw-r--r--src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java2
-rw-r--r--src/com/android/launcher3/allapps/AllAppsContainerView.java33
-rw-r--r--src/com/android/launcher3/allapps/AllAppsRecyclerView.java40
-rw-r--r--src/com/android/launcher3/allapps/AllAppsTransitionController.java9
-rw-r--r--src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java3
-rw-r--r--src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java56
-rw-r--r--src/com/android/launcher3/compat/WallpaperColorsCompat.java1
-rw-r--r--src/com/android/launcher3/dynamicui/ColorExtractionAlgorithm.java59
-rw-r--r--src/com/android/launcher3/dynamicui/WallpaperColorInfo.java6
-rw-r--r--src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java69
-rw-r--r--src/com/android/launcher3/folder/Folder.java114
-rw-r--r--src/com/android/launcher3/folder/FolderAnimationManager.java41
-rw-r--r--src/com/android/launcher3/folder/FolderIcon.java279
-rw-r--r--src/com/android/launcher3/folder/FolderIconPreviewVerifier.java40
-rw-r--r--src/com/android/launcher3/folder/FolderPagedView.java197
-rw-r--r--src/com/android/launcher3/folder/FolderPreviewItemAnim.java30
-rw-r--r--src/com/android/launcher3/folder/PreviewItemManager.java354
-rw-r--r--src/com/android/launcher3/folder/StackFolderIconLayoutRule.java15
-rw-r--r--src/com/android/launcher3/graphics/GradientView.java87
-rw-r--r--src/com/android/launcher3/graphics/ScrimView.java115
-rw-r--r--src/com/android/launcher3/graphics/ShadowGenerator.java19
-rw-r--r--src/com/android/launcher3/logging/UserEventDispatcher.java79
-rw-r--r--src/com/android/launcher3/notification/NotificationFooterLayout.java5
-rw-r--r--src/com/android/launcher3/notification/NotificationItemView.java20
-rw-r--r--src/com/android/launcher3/notification/NotificationListener.java19
-rw-r--r--src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java2
-rw-r--r--src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java9
-rw-r--r--src/com/android/launcher3/popup/PopupContainerWithArrow.java40
-rw-r--r--src/com/android/launcher3/provider/RestoreDbTask.java2
-rw-r--r--src/com/android/launcher3/views/ButtonPreference.java70
-rw-r--r--src/com/android/launcher3/views/DoubleShadowBubbleTextView.java28
-rw-r--r--src/com/android/launcher3/widget/PendingAddShortcutInfo.java1
-rw-r--r--src/com/android/launcher3/widget/WidgetsBottomSheet.java4
45 files changed, 1304 insertions, 923 deletions
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index 70be7dae4..6e33d2a55 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -9,10 +9,12 @@ import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Handler;
+import android.support.annotation.WorkerThread;
import android.util.Log;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.util.ContentWriter;
public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
@@ -22,6 +24,12 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
if (AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED.equals(intent.getAction())) {
+ int hostId = intent.getIntExtra(AppWidgetManager.EXTRA_HOST_ID, 0);
+ Log.d(TAG, "Widget ID map received for host:" + hostId);
+ if (hostId != Launcher.APPWIDGET_HOST_ID) {
+ return;
+ }
+
final int[] oldIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
final int[] newIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (oldIds.length == newIds.length) {
@@ -42,11 +50,23 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
/**
* Updates the app widgets whose id has changed during the restore process.
*/
+ @WorkerThread
static void restoreAppWidgetIds(Context context, PendingResult asyncResult,
int[] oldWidgetIds, int[] newWidgetIds) {
+ AppWidgetHost appWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
+ if (!RestoreDbTask.isPending(context)) {
+ // Someone has already gone through our DB once, probably LoaderTask. Skip any further
+ // modifications of the DB.
+ Log.e(TAG, "Skipping widget ID remap as DB already in use");
+ for (int widgetId : newWidgetIds) {
+ Log.d(TAG, "Deleting widgetId: " + widgetId);
+ appWidgetHost.deleteAppWidgetId(widgetId);
+ }
+ asyncResult.finish();
+ return;
+ }
final ContentResolver cr = context.getContentResolver();
final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
- AppWidgetHost appWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
for (int i = 0; i < oldWidgetIds.length; i++) {
Log.i(TAG, "Widget state restore id " + oldWidgetIds[i] + " => " + newWidgetIds[i]);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 42b64ea83..aac80052e 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -860,10 +860,10 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
// Expand the background drawing bounds by the padding baked into the background drawable
mBackground.getPadding(mTempRect);
mBackground.setBounds(
- left - mTempRect.left,
- top - mTempRect.top,
- right + mTempRect.right,
- bottom + mTempRect.bottom);
+ left - mTempRect.left - getPaddingLeft(),
+ top - mTempRect.top - getPaddingTop(),
+ right + mTempRect.right + getPaddingRight(),
+ bottom + mTempRect.bottom + getPaddingBottom());
}
/**
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 7520be2f6..031bfe115 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -63,6 +63,8 @@ public class DeviceProfile {
*/
private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f;
+ private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 1.82f;
+
// Overview mode
private final int overviewModeMinIconZoneHeightPx;
private final int overviewModeMaxIconZoneHeightPx;
@@ -71,7 +73,8 @@ public class DeviceProfile {
private final float overviewModeIconZoneRatio;
// Workspace
- private int desiredWorkspaceLeftRightMarginPx;
+ private final int desiredWorkspaceLeftRightMarginPx;
+ public final int cellLayoutPaddingLeftRightPx;
public final int edgeMarginPx;
public final Rect defaultWidgetPadding;
private final int defaultPageSpacingPx;
@@ -171,7 +174,9 @@ public class DeviceProfile {
this.getClass().getName());
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
- desiredWorkspaceLeftRightMarginPx = edgeMarginPx;
+ desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
+ cellLayoutPaddingLeftRightPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
pageIndicatorSizePx = res.getDimensionPixelSize(
R.dimen.dynamic_grid_min_page_indicator_size);
pageIndicatorLandGutterPx = res.getDimensionPixelSize(
@@ -233,17 +238,18 @@ public class DeviceProfile {
updateAvailableDimensions(dm, res);
// Now that we have all of the variables calculated, we can tune certain sizes.
- if (!isVerticalBarLayout()) {
+ float aspectRatio = ((float) Math.max(availableWidthPx, availableHeightPx))
+ / Math.min(availableWidthPx, availableHeightPx);
+ boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
+ if (!isVerticalBarLayout() && isPhone && isTallDevice) {
// We increase the page indicator size when there is extra space.
// ie. For a display with a large aspect ratio, we can keep the icons on the workspace
// in portrait mode closer together by increasing the page indicator size.
- int newPageIndicatorSizePx = getCellSize().y - iconSizePx - iconTextSizePx
- - iconDrawablePaddingOriginalPx;
- if (newPageIndicatorSizePx > pageIndicatorSizePx) {
- pageIndicatorSizePx = newPageIndicatorSizePx;
- // Recalculate the available dimensions using the new page indicator size.
- updateAvailableDimensions(dm, res);
- }
+ // Note: This calculation was created after noticing a pattern in the design spec.
+ pageIndicatorSizePx = getCellSize().y - iconSizePx - iconDrawablePaddingPx;
+
+ // Recalculate the available dimensions using the new page indicator size.
+ updateAvailableDimensions(dm, res);
}
computeAllAppsButtonSize(context);
@@ -260,9 +266,7 @@ public class DeviceProfile {
isLandscape);
// Hide labels on the workspace.
- profile.iconTextSizePx = 0;
- profile.cellHeightPx = profile.iconSizePx + profile.iconDrawablePaddingPx
- + Utilities.calculateTextHeight(profile.iconTextSizePx);
+ profile.adjustToHideWorkspaceLabels();
// We use these scales to measure and layout the widgets using their full invariant profile
// sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
@@ -286,6 +290,24 @@ public class DeviceProfile {
}
/**
+ * Adjusts the profile so that the labels on the Workspace are hidden.
+ * It is important to call this method after the All Apps variables have been set.
+ */
+ private void adjustToHideWorkspaceLabels() {
+ iconTextSizePx = 0;
+ iconDrawablePaddingPx = 0;
+ cellHeightPx = iconSizePx;
+
+ // In normal cases, All Apps cell height should equal the Workspace cell height.
+ // Since we are removing labels from the Workspace, we need to manually compute the
+ // All Apps cell height.
+ allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
+ + Utilities.calculateTextHeight(allAppsIconTextSizePx)
+ // Top and bottom padding is equal to the drawable padding
+ + allAppsIconDrawablePaddingPx * 2;
+ }
+
+ /**
* Determine the exact visual footprint of the all apps button, taking into account scaling
* and internal padding of the drawable.
*/
@@ -323,8 +345,7 @@ public class DeviceProfile {
if (isVerticalBarLayout()) {
// Always hide the Workspace text with vertical bar layout.
- iconTextSizePx = 0;
- allAppsCellHeightPx += Utilities.calculateTextHeight(allAppsIconTextSizePx);
+ adjustToHideWorkspaceLabels();
}
cellWidthPx = iconSizePx + iconDrawablePaddingPx;
@@ -428,7 +449,8 @@ public class DeviceProfile {
// Since we are only concerned with the overall padding, layout direction does
// not matter.
Point padding = getTotalWorkspacePadding();
- result.x = calculateCellWidth(availableWidthPx - padding.x, inv.numColumns);
+ int cellPadding = cellLayoutPaddingLeftRightPx * 2;
+ result.x = calculateCellWidth(availableWidthPx - padding.x - cellPadding, inv.numColumns);
result.y = calculateCellHeight(availableHeightPx - padding.y, inv.numRows);
return result;
}
@@ -513,7 +535,7 @@ public class DeviceProfile {
// In portrait, we want the pages spaced such that there is no
// overhang of the previous / next page into the current page viewport.
// We assume symmetrical padding in portrait mode.
- return Math.max(defaultPageSpacingPx, getWorkspacePadding(null).left / 2 + 1);
+ return Math.max(defaultPageSpacingPx, getWorkspacePadding(null).left + 1);
}
}
@@ -564,7 +586,7 @@ public class DeviceProfile {
lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
lp.width = searchBarBounds.x;
lp.height = searchBarBounds.y;
- lp.topMargin = mInsets.top + edgeMarginPx / 2;
+ lp.topMargin = mInsets.top + edgeMarginPx;
searchBar.setLayoutParams(lp);
// Layout the workspace
@@ -595,15 +617,18 @@ public class DeviceProfile {
? hotseatBarLeftNavBarRightPaddingPx
: hotseatBarRightNavBarRightPaddingPx;
- hotseat.getLayout().setPadding(mInsets.left, mInsets.top, mInsets.right + paddingRight,
+ hotseat.getLayout().setPadding(mInsets.left + cellLayoutPaddingLeftRightPx,
+ mInsets.top, mInsets.right + paddingRight + cellLayoutPaddingLeftRightPx,
workspacePadding.bottom);
} else if (isTablet) {
// Pad the hotseat with the workspace padding calculated above
lp.gravity = Gravity.BOTTOM;
lp.width = LayoutParams.MATCH_PARENT;
lp.height = hotseatBarHeightPx + mInsets.bottom;
- hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left,
- hotseatBarTopPaddingPx, hotseatAdjustment + workspacePadding.right,
+ hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left
+ + cellLayoutPaddingLeftRightPx,
+ hotseatBarTopPaddingPx,
+ hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx,
hotseatBarBottomPaddingPx + mInsets.bottom);
} else {
// For phones, layout the hotseat without any bottom margin
@@ -611,8 +636,10 @@ public class DeviceProfile {
lp.gravity = Gravity.BOTTOM;
lp.width = LayoutParams.MATCH_PARENT;
lp.height = hotseatBarHeightPx + mInsets.bottom;
- hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left,
- hotseatBarTopPaddingPx, hotseatAdjustment + workspacePadding.right,
+ hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left
+ + cellLayoutPaddingLeftRightPx,
+ hotseatBarTopPaddingPx,
+ hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx,
hotseatBarBottomPaddingPx + mInsets.bottom);
}
hotseat.setLayoutParams(lp);
@@ -652,7 +679,7 @@ public class DeviceProfile {
// Layout the AllAppsRecyclerView
View view = launcher.findViewById(R.id.apps_list_view);
- int paddingLeftRight = hasVerticalBarLayout ? 0 : edgeMarginPx;
+ int paddingLeftRight = desiredWorkspaceLeftRightMarginPx + cellLayoutPaddingLeftRightPx;
view.setPadding(paddingLeftRight, view.getPaddingTop(), paddingLeftRight,
view.getPaddingBottom());
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 7959d40d4..21254ab29 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -45,12 +45,6 @@ public class FolderInfo extends ItemInfo {
*/
public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004;
- /**
- * The folder items ranks have been updated such that they appear unchanged with the new
- * permutation display logic.
- */
- public static final int FLAG_ITEM_RANKS_UPDATED = 0x00000008;
-
public int options;
/**
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 4b71ba01d..3bcd7afb4 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -68,7 +68,7 @@ public class IconCache {
private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
// Empty class name is used for storing package default entry.
- private static final String EMPTY_CLASS_NAME = ".";
+ public static final String EMPTY_CLASS_NAME = ".";
private static final boolean DEBUG = false;
private static final boolean DEBUG_IGNORE_CACHE = false;
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 11c5309f2..c5be096a2 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -137,7 +137,18 @@ public class ItemInfo {
}
public ComponentName getTargetComponent() {
- return getIntent() == null ? null : getIntent().getComponent();
+ Intent intent = getIntent();
+ if (intent == null) {
+ return null;
+ }
+ ComponentName cn = intent.getComponent();
+ if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT && cn == null) {
+ // Legacy shortcuts may not have a componentName but just a packageName. In that case
+ // create a dummy componentName instead of adding additional check everywhere.
+ String pkg = intent.getPackage();
+ return pkg == null ? null : new ComponentName(pkg, IconCache.EMPTY_CLASS_NAME);
+ }
+ return cn;
}
public void writeToValues(ContentWriter writer) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 6ae4068c8..7b7177e54 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -274,6 +274,7 @@ public class Launcher extends BaseActivity
private boolean mHasFocus = false;
private ObjectAnimator mScrimAnimator;
+ private boolean mShouldFadeInScrim;
private PopupDataProvider mPopupDataProvider;
@@ -467,8 +468,12 @@ public class Launcher extends BaseActivity
mLauncherCallbacks.onCreate(savedInstanceState);
}
- // Listen for broadcasts screen off
- registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ // Listen for broadcasts
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
+ registerReceiver(mReceiver, filter);
+ mShouldFadeInScrim = true;
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
@@ -906,7 +911,7 @@ public class Launcher extends BaseActivity
NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
}
- if (mIsResumeFromActionScreenOff && mDragLayer.getBackground() != null) {
+ if (mShouldFadeInScrim && mDragLayer.getBackground() != null) {
if (mScrimAnimator != null) {
mScrimAnimator.cancel();
}
@@ -923,6 +928,7 @@ public class Launcher extends BaseActivity
mScrimAnimator.setStartDelay(getWindow().getTransitionBackgroundFadeDuration());
mScrimAnimator.start();
}
+ mShouldFadeInScrim = false;
}
@Override
@@ -1534,6 +1540,11 @@ public class Launcher extends BaseActivity
}
}
mIsResumeFromActionScreenOff = true;
+ mShouldFadeInScrim = true;
+ } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
+ // ACTION_USER_PRESENT is sent after onStart/onResume. This covers the case where
+ // the user unlocked and the Launcher is not in the foreground.
+ mShouldFadeInScrim = false;
}
}
};
@@ -2257,7 +2268,7 @@ public class Launcher extends BaseActivity
onClickFolderIcon(v);
}
} else if ((FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && v instanceof PageIndicator) ||
- (v == mAllAppsButton && mAllAppsButton != null)) {
+ (v == mAllAppsButton && mAllAppsButton != null)) {
onClickAllAppsButton(v);
} else if (tag instanceof AppInfo) {
startAppShortcutOrInfoActivity(v);
@@ -2308,8 +2319,9 @@ public class Launcher extends BaseActivity
}
/**
- * Event handler for the "grid" button that appears on the home screen, which
- * enters all apps mode.
+ * Event handler for the "grid" button or "caret" that appears on the home screen, which
+ * enters all apps mode. In verticalBarLayout the caret can be seen when all apps is open, and
+ * so in that case reverses the action.
*
* @param v The view that was clicked.
*/
@@ -2319,6 +2331,8 @@ public class Launcher extends BaseActivity
getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
ControlType.ALL_APPS_BUTTON);
showAppsView(true /* animated */, true /* updatePredictedApps */);
+ } else {
+ showWorkspace(true);
}
}
@@ -3046,7 +3060,6 @@ public class Launcher extends BaseActivity
List<ComponentKey> apps = mLauncherCallbacks.getPredictedApps();
if (apps != null) {
mAppsView.setPredictedApps(apps);
- getUserEventDispatcher().setPredictedApps(apps);
}
}
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index a4cd1913e..4813571f5 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -56,7 +56,6 @@ import com.android.launcher3.LauncherSettings.WorkspaceScreens;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dynamicui.ExtractionUtils;
-import com.android.launcher3.folder.FolderPagedView;
import com.android.launcher3.graphics.IconShapeOverride;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.DbDowngradeHelper;
@@ -726,87 +725,6 @@ public class LauncherProvider extends ContentProvider {
+ "';l.profile=" + serial + ";', ';') where itemType = 0;";
db.execSQL(sql);
}
-
- updateExistingFoldersToMatchPrePermutationLayout(db);
- }
-
- /**
- * We have changed the way we display items in Folders, but we want existing folders to
- * appear the same.
- *
- * To make this change invisible to existing Folders, we need to update the ranks of the
- * items such that, when displayed using the permutation, the order remains the same.
- */
- private void updateExistingFoldersToMatchPrePermutationLayout(SQLiteDatabase db) {
- InvariantDeviceProfile idp = new InvariantDeviceProfile(mContext);
- int maxCols = idp.numFolderColumns;
- int maxRows = idp.numFolderRows;
-
- try (SQLiteTransaction t = new SQLiteTransaction(db)) {
- Cursor c = db.query(Favorites.TABLE_NAME,
- new String[] {Favorites._ID, Favorites.OPTIONS},
- "itemType=" + Favorites.ITEM_TYPE_FOLDER, null, null, null, null);
-
- // For every Folder
- while (c.moveToNext()) {
- final long folderId = c.getLong(c.getColumnIndexOrThrow(Favorites._ID));
- int options = c.getInt(c.getColumnIndexOrThrow(Favorites.OPTIONS));
-
- if ((options & FolderInfo.FLAG_ITEM_RANKS_UPDATED) != 0) {
- // The folder has already been updated.
- continue;
- }
-
- // For each item in the Folder
- Cursor c2 = db.query(Favorites.TABLE_NAME, new String[] {
- Favorites._ID, Favorites.RANK, Favorites.CELLX, Favorites.CELLY,
- Favorites.OPTIONS},
- "container=" + folderId, null, null, null, null);
-
- int numItemsInFolder = c2.getCount();
-
- // Calculate the grid size.
- int[] gridXY = new int[2];
- FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, maxCols, maxRows,
- maxCols * maxRows, gridXY);
- int gridX = gridXY[0];
- int gridY = gridXY[1];
- int maxItemsPerPage = gridX * gridY;
-
- // We create a mapping from the permutation to the original rank (ie. the
- // inverse permutation). This is what we'll use to set the folder items so that
- // they appear in their original order.
- int[] inversion = new int[numItemsInFolder];
- for (int i = 0; i < numItemsInFolder; ++i) {
- int permutation = FolderPagedView.getReadingOrderPosForRank(i,
- maxItemsPerPage, gridX, null);
- inversion[permutation] = i;
- }
-
- // Now we update the ranks of the folder items. Note that cellX/cellY stay the
- // same, due to the permutation.
- for (int i = 0; i < numItemsInFolder && c2.moveToNext(); ++i) {
- final int rank = c2.getInt(c2.getColumnIndexOrThrow(Favorites.RANK));
-
- SQLiteStatement updateItem = db.compileStatement(
- "UPDATE favorites SET rank=" + inversion[rank] + " WHERE _id=?");
- updateItem.bindLong(1, c2.getInt(c2.getColumnIndexOrThrow(Favorites._ID)));
- updateItem.executeUpdateDelete();
- }
- c2.close();
-
- // Mark the folder as having been updated.
- options |= FolderInfo.FLAG_ITEM_RANKS_UPDATED;
- SQLiteStatement updateFolder = db.compileStatement(
- "UPDATE favorites SET options=" + options + " WHERE _id=?");
- updateFolder.bindLong(1, folderId);
- updateFolder.executeUpdateDelete();
- }
- c.close();
- t.commit();
- } catch (SQLException ex) {
- Log.w(TAG, "Error updating folder items to match permutation.", ex);
- }
}
@Override
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index e7349f040..44b9704f2 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -387,7 +387,6 @@ public class LauncherStateTransitionAnimation {
private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
final Workspace.State toWorkspaceState, final boolean animated, int type,
final Runnable onCompleteRunnable) {
- final AllAppsContainerView appsView = mLauncher.getAppsView();
// No alpha anim from all apps
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
@Override
@@ -417,12 +416,11 @@ public class LauncherStateTransitionAnimation {
@Override
void onTransitionComplete() {
mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
- appsView.reset();
}
};
// Only animate the search bar if animating to spring loaded mode from all apps
startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
- mLauncher.getStartViewForAllAppsRevealAnimation(), appsView,
+ mLauncher.getStartViewForAllAppsRevealAnimation(), mLauncher.getAppsView(),
animated, type, onCompleteRunnable, cb);
}
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index a34e469ea..d5d5eab76 100644
--- a/src/com/android/launcher3/SettingsActivity.java
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -17,7 +17,15 @@
package com.android.launcher3;
import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.ComponentName;
import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
import android.database.ContentObserver;
import android.os.Bundle;
import android.os.Handler;
@@ -26,8 +34,11 @@ import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.provider.Settings;
import android.provider.Settings.System;
+import android.view.View;
import com.android.launcher3.graphics.IconShapeOverride;
+import com.android.launcher3.notification.NotificationListener;
+import com.android.launcher3.views.ButtonPreference;
/**
* Settings activity for Launcher. Currently implements the following setting: Allow rotation
@@ -37,6 +48,8 @@ public class SettingsActivity extends Activity {
private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging";
// TODO: use Settings.Secure.NOTIFICATION_BADGING
private static final String NOTIFICATION_BADGING = "notification_badging";
+ /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
+ private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -85,17 +98,22 @@ public class SettingsActivity extends Activity {
rotationPref.setDefaultValue(Utilities.getAllowRotationDefaultValue(getActivity()));
}
- Preference iconBadgingPref = findPreference(ICON_BADGING_PREFERENCE_KEY);
+ ButtonPreference iconBadgingPref =
+ (ButtonPreference) findPreference(ICON_BADGING_PREFERENCE_KEY);
if (!Utilities.isAtLeastO()) {
getPreferenceScreen().removePreference(
findPreference(SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY));
getPreferenceScreen().removePreference(iconBadgingPref);
} else {
// Listen to system notification badge settings while this UI is active.
- mIconBadgingObserver = new IconBadgingObserver(iconBadgingPref, resolver);
+ mIconBadgingObserver = new IconBadgingObserver(
+ iconBadgingPref, resolver, getFragmentManager());
resolver.registerContentObserver(
Settings.Secure.getUriFor(NOTIFICATION_BADGING),
false, mIconBadgingObserver);
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS),
+ false, mIconBadgingObserver);
mIconBadgingObserver.onChange(true);
}
@@ -153,24 +171,74 @@ public class SettingsActivity extends Activity {
* Content observer which listens for system badging setting changes,
* and updates the launcher badging setting subtext accordingly.
*/
- private static class IconBadgingObserver extends ContentObserver {
+ private static class IconBadgingObserver extends ContentObserver
+ implements View.OnClickListener {
- private final Preference mBadgingPref;
+ private final ButtonPreference mBadgingPref;
private final ContentResolver mResolver;
+ private final FragmentManager mFragmentManager;
- public IconBadgingObserver(Preference badgingPref, ContentResolver resolver) {
+ public IconBadgingObserver(ButtonPreference badgingPref, ContentResolver resolver,
+ FragmentManager fragmentManager) {
super(new Handler());
mBadgingPref = badgingPref;
mResolver = resolver;
+ mFragmentManager = fragmentManager;
}
@Override
public void onChange(boolean selfChange) {
boolean enabled = Settings.Secure.getInt(mResolver, NOTIFICATION_BADGING, 1) == 1;
- mBadgingPref.setSummary(enabled
- ? R.string.icon_badging_desc_on
- : R.string.icon_badging_desc_off);
+ int summary = enabled ? R.string.icon_badging_desc_on : R.string.icon_badging_desc_off;
+
+ boolean serviceEnabled = true;
+ if (enabled) {
+ // Check if the listener is enabled or not.
+ String enabledListeners =
+ Settings.Secure.getString(mResolver, NOTIFICATION_ENABLED_LISTENERS);
+ ComponentName myListener =
+ new ComponentName(mBadgingPref.getContext(), NotificationListener.class);
+ serviceEnabled = enabledListeners != null &&
+ (enabledListeners.contains(myListener.flattenToString()) ||
+ enabledListeners.contains(myListener.flattenToShortString()));
+ if (!serviceEnabled) {
+ summary = R.string.title_missing_notification_access;
+ }
+ }
+ mBadgingPref.setButtonOnClickListener(serviceEnabled ? null : this);
+ mBadgingPref.setSummary(summary);
+
+ }
+
+ @Override
+ public void onClick(View view) {
+ new NotificationAccessConfirmation().show(mFragmentManager, "notification_access");
}
}
+ public static class NotificationAccessConfirmation
+ extends DialogFragment implements DialogInterface.OnClickListener {
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+ String msg = context.getString(R.string.msg_missing_notification_access,
+ context.getString(R.string.derived_app_name));
+ return new AlertDialog.Builder(context)
+ .setTitle(R.string.title_missing_notification_access)
+ .setMessage(msg)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(R.string.title_change_settings, this)
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ ComponentName cn = new ComponentName(getActivity(), NotificationListener.class);
+ Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(":settings:fragment_args_key", cn.flattenToString());
+ getActivity().startActivity(intent);
+ }
+ }
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 9100fe28d..3aa2db000 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -28,7 +28,6 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
@@ -654,27 +653,4 @@ public final class Utilities {
return hashSet;
}
- /**
- * @return creates a new alpha mask bitmap out of an existing bitmap
- */
- public static Bitmap convertToAlphaMask(Bitmap b, int applyAlpha) {
- Bitmap a = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ALPHA_8);
- Canvas c = new Canvas(a);
- Paint paint = new Paint();
- paint.setAlpha(applyAlpha);
- c.drawBitmap(b, 0f, 0f, paint);
- return a;
- }
-
- /**
- * @return a new white 1x1 bitmap with ALPHA_8
- */
- public static Bitmap createOnePixBitmap() {
- Bitmap a = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
- Canvas c = new Canvas(a);
- Paint paint = new Paint();
- paint.setColor(Color.WHITE);
- c.drawPaint(paint);
- return a;
- }
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 28070b7fa..f781a3d1d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -666,6 +666,10 @@ public class Workspace extends PagedView
newScreen.setOnLongClickListener(mLongClickListener);
newScreen.setOnClickListener(mLauncher);
newScreen.setSoundEffectsEnabled(false);
+
+ int paddingLeftRight = mLauncher.getDeviceProfile().cellLayoutPaddingLeftRightPx;
+ newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, 0);
+
mWorkspaceScreens.put(screenId, newScreen);
mScreenOrder.add(insertIndex, screenId);
addView(newScreen, insertIndex);
@@ -1397,7 +1401,7 @@ public class Workspace extends PagedView
@Override
protected boolean shouldFlingForVelocity(int velocityX) {
// When the overlay is moving, the fling or settle transition is controlled by the overlay.
- return Float.compare(mOverlayTranslation, 0) == 0 &&
+ return Float.compare(Math.abs(mOverlayTranslation), 0) == 0 &&
super.shouldFlingForVelocity(velocityX);
}
@@ -1632,16 +1636,21 @@ public class Workspace extends PagedView
}
private void updatePageAlphaValues() {
- if (mWorkspaceFadeInAdjacentScreens &&
- !workspaceInModalState() &&
- !mIsSwitchingState) {
+ if (!workspaceInModalState() && !mIsSwitchingState) {
int screenCenter = getScrollX() + getViewportWidth() / 2;
for (int i = numCustomPages(); i < getChildCount(); i++) {
CellLayout child = (CellLayout) getChildAt(i);
if (child != null) {
float scrollProgress = getScrollProgress(screenCenter, child, i);
float alpha = 1 - Math.abs(scrollProgress);
- child.getShortcutsAndWidgets().setAlpha(alpha);
+ if (mWorkspaceFadeInAdjacentScreens) {
+ child.getShortcutsAndWidgets().setAlpha(alpha);
+ } else {
+ // Pages that are off-screen aren't important for accessibility.
+ child.getShortcutsAndWidgets().setImportantForAccessibility(
+ alpha > 0 ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ }
}
}
}
@@ -2370,7 +2379,7 @@ public class Workspace extends PagedView
fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
postAnimationRunnable);
} else {
- fi.prepareCreate(v);
+ fi.prepareCreateAnimation(v);
fi.addItem(destInfo);
fi.addItem(sourceInfo);
}
@@ -3819,7 +3828,7 @@ public class Workspace extends PagedView
ItemInfo info = (ItemInfo) item.getTag();
if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) {
FolderIcon folder = (FolderIcon) item;
- ArrayList<View> folderChildren = folder.getFolder().getItemsInRankOrder();
+ ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
// map over all the children in the folder
final int childCount = folderChildren.size();
for (int childIdx = 0; childIdx < childCount; childIdx++) {
@@ -3979,8 +3988,9 @@ public class Workspace extends PagedView
}
}
- private void moveToScreen(int page, boolean animate) {
- if (!workspaceInModalState()) {
+ void moveToDefaultScreen(boolean animate) {
+ int page = getDefaultPage();
+ if (!workspaceInModalState() && getNextPage() != page) {
if (animate) {
snapToPage(page);
} else {
@@ -3993,10 +4003,6 @@ public class Workspace extends PagedView
}
}
- void moveToDefaultScreen(boolean animate) {
- moveToScreen(getDefaultPage(), animate);
- }
-
void moveToCustomContentScreen(boolean animate) {
if (hasCustomContent()) {
int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 6cd086b38..34335330b 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -369,7 +369,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme
Folder folder = Folder.getOpen(mLauncher);
if (folder != null) {
- if (!folder.getItemsInRankOrder().contains(item)) {
+ if (!folder.getItemsInReadingOrder().contains(item)) {
folder.close(true);
folder = null;
}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index ccef4f2f5..0083d47f2 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -21,7 +21,6 @@ import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.InsetDrawable;
import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.util.AttributeSet;
@@ -227,7 +226,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
mAppsRecyclerView.setHasFixedSize(true);
if (FeatureFlags.LAUNCHER3_PHYSICS) {
mAppsRecyclerView.setSpringAnimationHandler(mSpringAnimationHandler);
- mAppsRecyclerView.addOnScrollListener(new SpringMotionOnScrollListener());
}
mSearchContainer = findViewById(R.id.search_container_all_apps);
@@ -359,7 +357,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
@Override
public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
- targetParent.containerType = mAppsRecyclerView.getContainerType(v);
+ // This is filled in {@link AllAppsRecyclerView}
}
@Override
@@ -403,33 +401,4 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
public SpringAnimationHandler getSpringAnimationHandler() {
return mSpringAnimationHandler;
}
-
- public class SpringMotionOnScrollListener extends RecyclerView.OnScrollListener {
-
- private int mScrollState = RecyclerView.SCROLL_STATE_IDLE;
-
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- if (mScrollState == RecyclerView.SCROLL_STATE_DRAGGING || (dx == 0 && dy == 0)) {
- if (mSpringAnimationHandler.isRunning()){
- mSpringAnimationHandler.skipToEnd();
- }
- return;
- }
-
- // We only start the spring animation when we fling and hit the top/bottom, to ensure
- // that all of the animations start at the same time.
- if (dy < 0 && !mAppsRecyclerView.canScrollVertically(-1)) {
- mSpringAnimationHandler.animateToFinalPosition(0, 1);
- } else if (dy > 0 && !mAppsRecyclerView.canScrollVertically(1)) {
- mSpringAnimationHandler.animateToFinalPosition(0, -1);
- }
- }
-
- @Override
- public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
- super.onScrollStateChanged(recyclerView, newState);
- mScrollState = newState;
- }
- }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index fb785fbb0..701402b02 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -30,18 +30,21 @@ import android.view.View;
import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.ItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.anim.SpringAnimationHandler;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import java.util.List;
/**
* A RecyclerView with custom fast scroll support for the all apps view.
*/
-public class AllAppsRecyclerView extends BaseRecyclerView {
+public class AllAppsRecyclerView extends BaseRecyclerView implements LogContainerProvider {
private AlphabeticalAppsList mApps;
private AllAppsFastScrollHelper mFastScrollHelper;
@@ -100,7 +103,10 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
}
public void setSpringAnimationHandler(SpringAnimationHandler springAnimationHandler) {
- mSpringAnimationHandler = springAnimationHandler;
+ if (FeatureFlags.LAUNCHER3_PHYSICS) {
+ mSpringAnimationHandler = springAnimationHandler;
+ addOnScrollListener(new SpringMotionOnScrollListener());
+ }
}
@Override
@@ -231,9 +237,10 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
updateEmptySearchBackgroundBounds();
}
- public int getContainerType(View v) {
+ @Override
+ public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
if (mApps.hasFilter()) {
- return ContainerType.SEARCHRESULT;
+ targetParent.containerType = ContainerType.SEARCHRESULT;
} else {
if (v instanceof BubbleTextView) {
BubbleTextView icon = (BubbleTextView) v;
@@ -242,11 +249,13 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
AlphabeticalAppsList.AdapterItem item = items.get(position);
if (item.viewType == AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON) {
- return ContainerType.PREDICTION;
+ targetParent.containerType = ContainerType.PREDICTION;
+ target.predictedRank = item.rowAppIndex;
+ return;
}
}
}
- return ContainerType.ALLAPPS;
+ targetParent.containerType = ContainerType.ALLAPPS;
}
}
@@ -479,6 +488,25 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
y + mEmptySearchBackground.getIntrinsicHeight());
}
+ private class SpringMotionOnScrollListener extends RecyclerView.OnScrollListener {
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ if (mOverScrollHelper.isInOverScroll()) {
+ // OverScroll will handle animating the springs.
+ return;
+ }
+
+ // We only start the spring animation when we hit the top/bottom, to ensure
+ // that all of the animations start at the same time.
+ if (dy < 0 && !canScrollVertically(-1)) {
+ mSpringAnimationHandler.animateToFinalPosition(0, 1);
+ } else if (dy > 0 && !canScrollVertically(1)) {
+ mSpringAnimationHandler.animateToFinalPosition(0, -1);
+ }
+ }
+ }
+
private class OverScrollHelper implements VerticalPullDetector.Listener {
private static final float MAX_RELEASE_VELOCITY = 5000; // px / s
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 0859e0658..44e0a0474 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -25,7 +25,6 @@ import com.android.launcher3.Workspace;
import com.android.launcher3.anim.SpringAnimationHandler;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.GradientView;
-import com.android.launcher3.graphics.ScrimView;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.SystemUiController;
@@ -100,7 +99,6 @@ public class AllAppsTransitionController implements TouchController, VerticalPul
private boolean mIsTranslateWithoutWorkspace = false;
private AnimatorSet mDiscoBounceAnimation;
private GradientView mGradientView;
- private ScrimView mScrimView;
private SpringAnimationHandler mSpringAnimationHandler;
@@ -302,13 +300,6 @@ public class AllAppsTransitionController implements TouchController, VerticalPul
mGradientView.setVisibility(View.VISIBLE);
}
mGradientView.setProgress(progress);
-
- // scrim
- if (mScrimView == null) {
- mScrimView = (ScrimView) mLauncher.findViewById(R.id.scrim_bg);
- mScrimView.setVisibility(View.VISIBLE);
- }
- mScrimView.setProgress(progress);
}
/**
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 5cb12d592..39e208874 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -202,7 +202,8 @@ public class AppsSearchContainerLayout extends FrameLayout
if (!dp.isVerticalBarLayout()) {
Rect insets = mLauncher.getDragLayer().getInsets();
int hotseatBottom = bottom - dp.hotseatBarBottomPaddingPx - insets.bottom;
- int searchTopMargin = insets.top + (mMinHeight - mSearchBoxHeight);
+ int searchTopMargin = insets.top + (mMinHeight - mSearchBoxHeight)
+ + ((MarginLayoutParams) getLayoutParams()).bottomMargin;
listener.onScrollRangeChanged(hotseatBottom - searchTopMargin);
} else {
listener.onScrollRangeChanged(bottom);
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index 8a0fd46f7..21eb3fba0 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -20,6 +20,7 @@ import android.os.Handler;
import com.android.launcher3.AppInfo;
import com.android.launcher3.util.ComponentKey;
+import java.text.Collator;
import java.util.ArrayList;
import java.util.List;
@@ -61,8 +62,9 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
// apps that don't match all of the words in the query.
final String queryTextLower = query.toLowerCase();
final ArrayList<ComponentKey> result = new ArrayList<>();
+ StringMatcher matcher = StringMatcher.getInstance();
for (AppInfo info : mApps) {
- if (matches(info, queryTextLower)) {
+ if (matches(info, queryTextLower, matcher)) {
result.add(info.toComponentKey());
}
}
@@ -70,6 +72,10 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
}
public static boolean matches(AppInfo info, String query) {
+ return matches(info, query, StringMatcher.getInstance());
+ }
+
+ public static boolean matches(AppInfo info, String query, StringMatcher matcher) {
int queryLength = query.length();
String title = info.title.toString();
@@ -90,7 +96,7 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
nextType = i < (titleLength - 1) ?
Character.getType(title.codePointAt(i + 1)) : Character.UNASSIGNED;
if (isBreak(thisType, lastType, nextType) &&
- title.substring(i, i + queryLength).equalsIgnoreCase(query)) {
+ matcher.matches(query, title.substring(i, i + queryLength))) {
return true;
}
}
@@ -106,6 +112,13 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
* 4) Any capital character before a small character
*/
private static boolean isBreak(int thisType, int prevType, int nextType) {
+ switch (prevType) {
+ case Character.UNASSIGNED:
+ case Character.SPACE_SEPARATOR:
+ case Character.LINE_SEPARATOR:
+ case Character.PARAGRAPH_SEPARATOR:
+ return true;
+ }
switch (thisType) {
case Character.UPPERCASE_LETTER:
if (nextType == Character.UPPERCASE_LETTER) {
@@ -132,7 +145,44 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
// Always a break point for a symbol
return true;
default:
- return false;
+ return false;
+ }
+ }
+
+ public static class StringMatcher {
+
+ private static final char MAX_UNICODE = '\uFFFF';
+
+ private final Collator mCollator;
+
+ StringMatcher() {
+ // On android N and above, Collator uses ICU implementation which has a much better
+ // support for non-latin locales.
+ mCollator = Collator.getInstance();
+ mCollator.setStrength(Collator.PRIMARY);
+ mCollator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
+ }
+
+ /**
+ * Returns true if {@param query} is a prefix of {@param target}
+ */
+ public boolean matches(String query, String target) {
+ switch (mCollator.compare(query, target)) {
+ case 0:
+ return true;
+ case -1:
+ // The target string can contain a modifier which would make it larger than
+ // the query string (even though the length is same). If the query becomes
+ // larger after appending a unicode character, it was originally a prefix of
+ // the target string and hence should match.
+ return mCollator.compare(query + MAX_UNICODE, target) > -1;
+ default:
+ return false;
+ }
+ }
+
+ public static StringMatcher getInstance() {
+ return new StringMatcher();
}
}
}
diff --git a/src/com/android/launcher3/compat/WallpaperColorsCompat.java b/src/com/android/launcher3/compat/WallpaperColorsCompat.java
index 58d2a8028..e25b9d929 100644
--- a/src/com/android/launcher3/compat/WallpaperColorsCompat.java
+++ b/src/com/android/launcher3/compat/WallpaperColorsCompat.java
@@ -21,6 +21,7 @@ package com.android.launcher3.compat;
public class WallpaperColorsCompat {
public static final int HINT_SUPPORTS_DARK_TEXT = 0x1;
+ public static final int HINT_SUPPORTS_DARK_THEME = 0x2;
private final int mPrimaryColor;
private final int mSecondaryColor;
diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionAlgorithm.java b/src/com/android/launcher3/dynamicui/ColorExtractionAlgorithm.java
index de614f031..baedf9063 100644
--- a/src/com/android/launcher3/dynamicui/ColorExtractionAlgorithm.java
+++ b/src/com/android/launcher3/dynamicui/ColorExtractionAlgorithm.java
@@ -50,19 +50,26 @@ public class ColorExtractionAlgorithm {
private static final float FIT_WEIGHT_S = 1.0f;
private static final float FIT_WEIGHT_L = 10.0f;
+ public static final int MAIN_COLOR_LIGHT = 0xffb0b0b0;
+ public static final int SECONDARY_COLOR_LIGHT = 0xff9e9e9e;
+ public static final int MAIN_COLOR_DARK = 0xff212121;
+ public static final int SECONDARY_COLOR_DARK = 0xff000000;
+
// Temporary variable to avoid allocations
private float[] mTmpHSL = new float[3];
- public @Nullable Pair<Integer, Integer> extractInto(WallpaperColorsCompat inWallpaperColors) {
+ public Pair<Integer, Integer> extractInto(WallpaperColorsCompat inWallpaperColors) {
if (inWallpaperColors == null) {
- return null;
+ return applyFallback(inWallpaperColors);
}
final List<Integer> mainColors = getMainColors(inWallpaperColors);
final int mainColorsSize = mainColors.size();
+ final boolean supportsDarkText = (inWallpaperColors.getColorHints() &
+ WallpaperColorsCompat.HINT_SUPPORTS_DARK_TEXT) != 0;
if (mainColorsSize == 0) {
- return null;
+ return applyFallback(inWallpaperColors);
}
// Tonal is not really a sort, it takes a color from the extracted
// palette and finds a best fit amongst a collection of pre-defined
@@ -86,7 +93,7 @@ public class ColorExtractionAlgorithm {
// Fail if not found
if (bestColor == null) {
- return null;
+ return applyFallback(inWallpaperColors);
}
int colorValue = bestColor;
@@ -101,14 +108,14 @@ public class ColorExtractionAlgorithm {
TonalPalette palette = findTonalPalette(hsl[0], hsl[1]);
if (palette == null) {
Log.w(TAG, "Could not find a tonal palette!");
- return null;
+ return applyFallback(inWallpaperColors);
}
// Figure out what's the main color index in the optimal palette
int fitIndex = bestFit(palette, hsl[0], hsl[1], hsl[2]);
if (fitIndex == -1) {
Log.w(TAG, "Could not find best fit!");
- return null;
+ return applyFallback(inWallpaperColors);
}
// Generate the 10 colors palette by offsetting each one of them
@@ -117,28 +124,48 @@ public class ColorExtractionAlgorithm {
float[] s = fit(palette.s, hsl[1], fitIndex, 0.0f, 1.0f);
float[] l = fit(palette.l, hsl[2], fitIndex, 0.0f, 1.0f);
- final int textInversionIndex = h.length - 3;
+ int primaryIndex = fitIndex;
+ int mainColor = getColorInt(primaryIndex, h, s, l);
- int primaryIndex;
- int secondaryIndex;
+ // We might want use the fallback in case the extracted color is brighter than our
+ // light fallback or darker than our dark fallback.
+ ColorUtils.colorToHSL(mainColor, mTmpHSL);
+ final float mainLuminosity = mTmpHSL[2];
+ ColorUtils.colorToHSL(MAIN_COLOR_LIGHT, mTmpHSL);
+ final float lightLuminosity = mTmpHSL[2];
+ if (mainLuminosity > lightLuminosity) {
+ return applyFallback(inWallpaperColors);
+ }
+ ColorUtils.colorToHSL(MAIN_COLOR_DARK, mTmpHSL);
+ final float darkLuminosity = mTmpHSL[2];
+ if (mainLuminosity < darkLuminosity) {
+ return applyFallback(inWallpaperColors);
+ }
// Dark colors:
// Stops at 4th color, only lighter if dark text is supported
- if (fitIndex < 2) {
+ if (supportsDarkText) {
+ primaryIndex = h.length - 1;
+ } else if (fitIndex < 2) {
primaryIndex = 0;
- } else if (fitIndex < textInversionIndex) {
- primaryIndex = Math.min(fitIndex, 3);
} else {
- primaryIndex = h.length - 1;
+ primaryIndex = Math.min(fitIndex, 3);
}
- secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2);
-
- int mainColor = getColorInt(primaryIndex, h, s, l);
+ int secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2);
int secondaryColor = getColorInt(secondaryIndex, h, s, l);
return new Pair<>(mainColor, secondaryColor);
}
+ public static Pair<Integer, Integer> applyFallback(WallpaperColorsCompat inWallpaperColors) {
+ boolean light = inWallpaperColors != null
+ && (inWallpaperColors.getColorHints()
+ & WallpaperColorsCompat.HINT_SUPPORTS_DARK_TEXT)!= 0;
+ int innerColor = light ? MAIN_COLOR_LIGHT : MAIN_COLOR_DARK;
+ int outerColor = light ? SECONDARY_COLOR_LIGHT : SECONDARY_COLOR_DARK;
+ return new Pair<>(innerColor, outerColor);
+ }
+
private int getColorInt(int fitIndex, float[] h, float[] s, float[] l) {
mTmpHSL[0] = fract(h[fitIndex]) * 360.0f;
mTmpHSL[1] = s[fitIndex];
diff --git a/src/com/android/launcher3/dynamicui/WallpaperColorInfo.java b/src/com/android/launcher3/dynamicui/WallpaperColorInfo.java
index 512e89a41..80a89e37d 100644
--- a/src/com/android/launcher3/dynamicui/WallpaperColorInfo.java
+++ b/src/com/android/launcher3/dynamicui/WallpaperColorInfo.java
@@ -81,9 +81,9 @@ public class WallpaperColorInfo implements WallpaperManagerCompat.OnColorsChange
mSupportsDarkText = wallpaperColors != null
? (wallpaperColors.getColorHints()
& WallpaperColorsCompat.HINT_SUPPORTS_DARK_TEXT) > 0 : false;
- float[] hsl = new float[3];
- ColorUtils.colorToHSL(mMainColor, hsl);
- mIsDark = hsl[2] < 0.2f;
+ mIsDark = wallpaperColors != null
+ ? (wallpaperColors.getColorHints()
+ & WallpaperColorsCompat.HINT_SUPPORTS_DARK_THEME) > 0 : false;
}
public void setOnThemeChangeListener(OnThemeChangeListener onThemeChangeListener) {
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index ff357c0bc..f25345ee8 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -1,15 +1,18 @@
package com.android.launcher3.folder;
+
public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
static final int MAX_NUM_ITEMS_IN_PREVIEW = 4;
private static final int MIN_NUM_ITEMS_IN_PREVIEW = 2;
- private static final int MAX_NUM_ITEMS_PER_ROW = 2;
- final float MIN_SCALE = 0.48f;
- final float MAX_SCALE = 0.58f;
- final float MAX_RADIUS_DILATION = 0.15f;
- final float ITEM_RADIUS_SCALE_FACTOR = 1.33f;
+ private static final float MIN_SCALE = 0.48f;
+ private static final float MAX_SCALE = 0.58f;
+ private static final float MAX_RADIUS_DILATION = 0.15f;
+ private static final float ITEM_RADIUS_SCALE_FACTOR = 1.33f;
+
+ private static final int EXIT_INDEX = -2;
+ private static final int ENTER_INDEX = -3;
private float[] mTmpPoint = new float[2];
@@ -31,21 +34,29 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule
@Override
public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
PreviewItemDrawingParams params) {
-
float totalScale = scaleForItem(index, curNumItems);
float transX;
float transY;
float overlayAlpha = 0;
- // Items beyond those displayed in the preview are animated to the center
- if (index >= MAX_NUM_ITEMS_IN_PREVIEW) {
- transX = transY = mAvailableSpace / 2 - (mIconSize * totalScale) / 2;
+ if (index == getExitIndex()) {
+ // 0 1 * <-- Exit position (row 0, col 2)
+ // 2 3
+ getGridPosition(0, 2, mTmpPoint);
+ } else if (index == getEnterIndex()) {
+ // 0 1
+ // 2 3 * <-- Enter position (row 1, col 2)
+ getGridPosition(1, 2, mTmpPoint);
+ } else if (index >= MAX_NUM_ITEMS_IN_PREVIEW) {
+ // Items beyond those displayed in the preview are animated to the center
+ mTmpPoint[0] = mTmpPoint[1] = mAvailableSpace / 2 - (mIconSize * totalScale) / 2;
} else {
getPosition(index, curNumItems, mTmpPoint);
- transX = mTmpPoint[0];
- transY = mTmpPoint[1];
}
+ transX = mTmpPoint[0];
+ transY = mTmpPoint[1];
+
if (params == null) {
params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
} else {
@@ -55,6 +66,27 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule
return params;
}
+ /**
+ * Builds a grid based on the positioning of the items when there are
+ * {@link #MAX_NUM_ITEMS_IN_PREVIEW} in the preview.
+ *
+ * Positions in the grid: 0 1 // 0 is row 0, col 1
+ * 2 3 // 3 is row 1, col 1
+ */
+ private void getGridPosition(int row, int col, float[] result) {
+ // We use position 0 and 3 to calculate the x and y distances between items.
+ getPosition(0, 4, result);
+ float left = result[0];
+ float top = result[1];
+
+ getPosition(3, 4, result);
+ float dx = result[0] - left;
+ float dy = result[1] - top;
+
+ result[0] = left + (col * dx);
+ result[1] = top + (row * dy);
+ }
+
private void getPosition(int index, int curNumItems, float[] result) {
// The case of two items is homomorphic to the case of one.
curNumItems = Math.max(curNumItems, 2);
@@ -127,4 +159,19 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule
public boolean clipToBackground() {
return true;
}
+
+ @Override
+ public boolean hasEnterExitIndices() {
+ return true;
+ }
+
+ @Override
+ public int getExitIndex() {
+ return EXIT_INDEX;
+ }
+
+ @Override
+ public int getEnterIndex() {
+ return ENTER_INDEX;
+ }
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index f68b394c1..85792d4cc 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -133,7 +133,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
private final Alarm mOnScrollHintAlarm = new Alarm();
@Thunk final Alarm mScrollPauseAlarm = new Alarm();
- @Thunk final ArrayList<View> mItemsInRankOrder = new ArrayList<>();
+ @Thunk final ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
private AnimatorSet mCurrentAnimator;
@@ -284,7 +284,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
if (tag instanceof ShortcutInfo) {
ShortcutInfo item = (ShortcutInfo) tag;
- mEmptyCellRank = mContent.getReadingOrderPosForRank(item.rank);
+ mEmptyCellRank = item.rank;
mCurrentDragView = v;
mDragController.addDragListener(this);
@@ -705,7 +705,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
}
public void beginExternalDrag() {
- mEmptyCellRank = mContent.getReadingOrderPosForRank(mContent.allocateRankForNewItem());
+ mEmptyCellRank = mContent.allocateRankForNewItem();
mIsExternalDrag = true;
mDragInProgress = true;
@@ -791,12 +791,15 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
if (mFolderIcon != null) {
mFolderIcon.setVisibility(View.VISIBLE);
if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) {
- mFolderIcon.mFolderName.setTextVisibility(true);
mFolderIcon.setBackgroundVisible(true);
- mFolderIcon.mBackground.fadeInBackgroundShadow();
+ mFolderIcon.mFolderName.setTextVisibility(true);
}
if (wasAnimated) {
- mFolderIcon.mBackground.animateBackgroundStroke();
+ if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) {
+ mFolderIcon.mBackground.fadeInBackgroundShadow();
+ mFolderIcon.mBackground.animateBackgroundStroke();
+ mFolderIcon.onFolderClose(mContent.getCurrentPage());
+ }
if (mFolderIcon.hasBadge()) {
mFolderIcon.createBadgeScaleAnimator(0f, 1f).start();
}
@@ -818,6 +821,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
mSuppressFolderDeletion = false;
clearDragInfo();
mState = STATE_SMALL;
+ mContent.setCurrentPage(0);
}
public boolean acceptDrop(DragObject d) {
@@ -997,7 +1001,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
ShortcutInfo info = (ShortcutInfo) d.dragInfo;
View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
? mCurrentDragView : mContent.createNewView(info);
- ArrayList<View> views = getItemsInRankOrder();
+ ArrayList<View> views = getItemsInReadingOrder();
views.add(info.rank, icon);
mContent.arrangeChildren(views, views.size());
mItemsInvalidated = true;
@@ -1072,7 +1076,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
}
private void updateItemLocationsInDatabaseBatch() {
- ArrayList<View> list = getItemsInRankOrder();
+ ArrayList<View> list = getItemsInReadingOrder();
ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
for (int i = 0; i < list.size(); i++) {
View v = list.get(i);
@@ -1231,7 +1235,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
* otherwise it is ignored.
*/
public void rearrangeChildren(int itemCount) {
- ArrayList<View> views = getItemsInRankOrder();
+ ArrayList<View> views = getItemsInReadingOrder();
mContent.arrangeChildren(views, Math.max(itemCount, views.size()));
mItemsInvalidated = true;
}
@@ -1280,7 +1284,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
};
View finalChild = mContent.getLastItem();
if (finalChild != null) {
- mFolderIcon.performDestroyAnimation(finalChild, onCompleteRunnable);
+ mFolderIcon.performDestroyAnimation(onCompleteRunnable);
} else {
onCompleteRunnable.run();
}
@@ -1355,8 +1359,11 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
}
mContent.completePendingPageChanges();
- if (d.dragInfo instanceof PendingAddShortcutInfo) {
- PendingAddShortcutInfo pasi = (PendingAddShortcutInfo) d.dragInfo;
+ PendingAddShortcutInfo pasi = d.dragInfo instanceof PendingAddShortcutInfo
+ ? (PendingAddShortcutInfo) d.dragInfo : null;
+ ShortcutInfo pasiSi = pasi != null ? pasi.activityInfo.createShortcutInfo() : null;
+ if (pasi != null && pasiSi == null) {
+ // There is no ShortcutInfo, so we have to go through a configuration activity.
pasi.container = mInfo.id;
pasi.rank = mEmptyCellRank;
@@ -1366,7 +1373,9 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
mRearrangeOnClose = true;
} else {
final ShortcutInfo si;
- if (d.dragInfo instanceof AppInfo) {
+ if (pasiSi != null) {
+ si = pasiSi;
+ } else if (d.dragInfo instanceof AppInfo) {
// Came from all apps -- make a copy.
si = ((AppInfo) d.dragInfo).makeShortcut();
} else {
@@ -1376,19 +1385,23 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
View currentDragView;
if (mIsExternalDrag) {
+ currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank);
+
// Actually move the item in the database if it was an external drag. Call this
// before creating the view, so that ShortcutInfo is updated appropriately.
- mLauncher.getModelWriter().addOrMoveItemInDatabase(si, mInfo.id, 0, si.cellX, si.cellY);
- }
+ mLauncher.getModelWriter().addOrMoveItemInDatabase(
+ si, mInfo.id, 0, si.cellX, si.cellY);
- currentDragView = mIsExternalDrag
- ? mContent.createNewView(si)
- : mCurrentDragView;
- mIsExternalDrag = false;
-
- // Note: addViewForRankDuringDragAndDrop handles rearranging the children.
- mContent.addViewForRankDuringDragAndDrop(currentDragView, si, mEmptyCellRank);
- mItemsInvalidated = true;
+ // We only need to update the locations if it doesn't get handled in
+ // #onDropCompleted.
+ if (d.dragSource != this) {
+ updateItemLocationsInDatabaseBatch();
+ }
+ mIsExternalDrag = false;
+ } else {
+ currentDragView = mCurrentDragView;
+ mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
+ }
if (d.dragView.hasDrawn()) {
// Temporarily reset the scale such that the animation target gets calculated
@@ -1406,6 +1419,9 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
currentDragView.setVisibility(VISIBLE);
}
+ mItemsInvalidated = true;
+ rearrangeChildren();
+
// Temporarily suppress the listener, as we did all the work already here.
try (SuppressInfoChanges s = new SuppressInfoChanges()) {
mInfo.add(si, false);
@@ -1443,7 +1459,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX,
item.cellY);
- ArrayList<View> items = new ArrayList<>(getItemsInRankOrder());
+ ArrayList<View> items = new ArrayList<>(getItemsInReadingOrder());
items.add(rank, view);
mContent.arrangeChildren(items, items.size());
mItemsInvalidated = true;
@@ -1490,44 +1506,33 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
public void onTitleChanged(CharSequence title) {
}
- public ArrayList<View> getItemsInRankOrder() {
+ public ArrayList<View> getItemsInReadingOrder() {
if (mItemsInvalidated) {
- mItemsInRankOrder.clear();
- mItemsInRankOrder.addAll(getItemsInReadingOrder());
- mItemsInRankOrder.sort(VIEW_RANK_COMPARATOR);
+ mItemsInReadingOrder.clear();
+ mContent.iterateOverItems(new ItemOperator() {
+ @Override
+ public boolean evaluate(ItemInfo info, View view) {
+ mItemsInReadingOrder.add(view);
+ return false;
+ }
+ });
mItemsInvalidated = false;
}
- return mItemsInRankOrder;
- }
-
- /**
- * This is an expensive call. Consider using {@link #getItemsInRankOrder()} instead.
- */
- public ArrayList<View> getItemsInReadingOrder() {
- final ArrayList<View> itemsInReadingOrder = new ArrayList<>();
- mContent.iterateOverItems(new ItemOperator() {
- @Override
- public boolean evaluate(ItemInfo info, View view) {
- itemsInReadingOrder.add(view);
- return false;
- }
- });
- return itemsInReadingOrder;
+ return mItemsInReadingOrder;
}
- public List<BubbleTextView> getItemsOnCurrentPage() {
- ArrayList<View> allItems = getItemsInRankOrder();
- int currentPage = mContent.getCurrentPage();
+ public List<BubbleTextView> getItemsOnPage(int page) {
+ ArrayList<View> allItems = getItemsInReadingOrder();
int lastPage = mContent.getPageCount() - 1;
int totalItemsInFolder = allItems.size();
int itemsPerPage = mContent.itemsPerPage();
- int numItemsOnCurrentPage = currentPage == lastPage
- ? totalItemsInFolder - (itemsPerPage * currentPage)
+ int numItemsOnCurrentPage = page == lastPage
+ ? totalItemsInFolder - (itemsPerPage * page)
: itemsPerPage;
- int startIndex = currentPage * itemsPerPage;
- int endIndex = startIndex + numItemsOnCurrentPage;
+ int startIndex = page * itemsPerPage;
+ int endIndex = Math.min(startIndex + numItemsOnCurrentPage, allItems.size());
List<BubbleTextView> itemsOnCurrentPage = new ArrayList<>(numItemsOnCurrentPage);
for (int i = startIndex; i < endIndex; ++i) {
@@ -1625,13 +1630,6 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
}
};
- public static final Comparator<View> VIEW_RANK_COMPARATOR = new Comparator<View>() {
- @Override
- public int compare(View lhs, View rhs) {
- return ITEM_POS_COMPARATOR.compare((ItemInfo) lhs.getTag(), (ItemInfo) rhs.getTag());
- }
- };
-
/**
* Temporary resource held while we don't want to handle info changes
*/
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 7e8d0c7cd..69705d594 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -119,13 +119,14 @@ public class FolderAnimationManager {
public AnimatorSet getAnimator() {
final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams();
FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule();
- final List<BubbleTextView> itemsInPreview = mFolderIcon.getItemsToDisplay();
+ final List<BubbleTextView> itemsInPreview = mFolderIcon.getPreviewItems();
// Match position of the FolderIcon
final Rect folderIconPos = new Rect();
float scaleRelativeToDragLayer = mLauncher.getDragLayer()
.getDescendantRectRelativeToSelf(mFolderIcon, folderIconPos);
- float initialSize = (mPreviewBackground.getRadius() * 2) * scaleRelativeToDragLayer;
+ int scaledRadius = mPreviewBackground.getScaledRadius();
+ float initialSize = (scaledRadius * 2) * scaleRelativeToDragLayer;
// Match size/scale of icons in the preview
float previewScale = rule.scaleForItem(0, itemsInPreview.size());
@@ -152,13 +153,9 @@ public class FolderAnimationManager {
final int paddingOffsetY = (int) ((mFolder.getPaddingTop() + mContent.getPaddingTop())
* initialScale);
- // Background can have a scaled radius in drag and drop mode.
- int radiusDiff = mPreviewBackground.getScaledRadius()- mPreviewBackground.getRadius();
-
int initialX = folderIconPos.left + mPreviewBackground.getOffsetX() - paddingOffsetX
- - previewItemOffsetX + radiusDiff;
- int initialY = folderIconPos.top + mPreviewBackground.getOffsetY() - paddingOffsetY
- + radiusDiff;
+ - previewItemOffsetX;
+ int initialY = folderIconPos.top + mPreviewBackground.getOffsetY() - paddingOffsetY;
final float xDistance = initialX - lp.x;
final float yDistance = initialY - lp.y;
@@ -186,7 +183,7 @@ public class FolderAnimationManager {
PropertyResetListener colorResetListener = new PropertyResetListener<>(
BubbleTextView.TEXT_ALPHA_PROPERTY,
Color.alpha(Themes.getAttrColor(mContext, android.R.attr.textColorSecondary)));
- for (BubbleTextView icon : mFolder.getItemsOnCurrentPage()) {
+ for (BubbleTextView icon : mFolder.getItemsOnPage(mFolder.mContent.getCurrentPage())) {
if (mIsOpening) {
icon.setTextVisibility(false);
}
@@ -232,18 +229,28 @@ public class FolderAnimationManager {
animator.setInterpolator(mFolderInterpolator);
}
- addPreviewItemAnimators(a, initialScale / scaleRelativeToDragLayer, previewItemOffsetX);
+ int radiusDiff = scaledRadius - mPreviewBackground.getRadius();
+ addPreviewItemAnimators(a, initialScale / scaleRelativeToDragLayer,
+ // Background can have a scaled radius in drag and drop mode, so we need to add the
+ // difference to keep the preview items centered.
+ previewItemOffsetX + radiusDiff, radiusDiff);
return a;
}
/**
- * Animate the items that are displayed in the preview.
+ * Animate the items on the current page.
*/
private void addPreviewItemAnimators(AnimatorSet animatorSet, final float folderScale,
- int previewItemOffsetX) {
+ int previewItemOffsetX, int previewItemOffsetY) {
FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule();
- final List<BubbleTextView> itemsInPreview = mFolderIcon.getItemsToDisplay();
+ boolean isOnFirstPage = mFolder.mContent.getCurrentPage() == 0;
+ final List<BubbleTextView> itemsInPreview = isOnFirstPage
+ ? mFolderIcon.getPreviewItems()
+ : mFolderIcon.getPreviewItemsOnPage(mFolder.mContent.getCurrentPage());
final int numItemsInPreview = itemsInPreview.size();
+ final int numItemsInFirstPagePreview = isOnFirstPage
+ ? numItemsInPreview
+ : FolderIcon.NUM_ITEMS_IN_PREVIEW;
TimeInterpolator previewItemInterpolator = getPreviewItemInterpolator();
@@ -256,8 +263,8 @@ public class FolderAnimationManager {
btvLp.isLockedToGrid = true;
cwc.setupLp(btv);
- // Match scale of icons in the preview.
- float previewScale = rule.scaleForItem(i, numItemsInPreview);
+ // Match scale of icons in the preview of the items on the first page.
+ float previewScale = rule.scaleForItem(i, numItemsInFirstPagePreview);
float previewSize = rule.getIconSize() * previewScale;
float iconScale = previewSize / itemsInPreview.get(i).getIconSize();
@@ -268,14 +275,14 @@ public class FolderAnimationManager {
btv.setScaleY(scale);
// Match positions of the icons in the folder with their positions in the preview
- rule.computePreviewItemDrawingParams(i, numItemsInPreview, mTmpParams);
+ rule.computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, mTmpParams);
// The PreviewLayoutRule assumes that the icon size takes up the entire width so we
// offset by the actual size.
int iconOffsetX = (int) ((btvLp.width - btv.getIconSize()) * iconScale) / 2;
final int previewPosX =
(int) ((mTmpParams.transX - iconOffsetX + previewItemOffsetX) / folderScale);
- final int previewPosY = (int) (mTmpParams.transY / folderScale);
+ final int previewPosY = (int) ((mTmpParams.transY + previewItemOffsetY) / folderScale);
final float xDistance = previewPosX - btvLp.x;
final float yDistance = previewPosY - btvLp.y;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 3a0e71fa5..84ec18410 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -37,7 +37,6 @@ import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
-import android.widget.TextView;
import com.android.launcher3.Alarm;
import com.android.launcher3.AppInfo;
@@ -71,6 +70,8 @@ import com.android.launcher3.widget.PendingAddShortcutInfo;
import java.util.ArrayList;
import java.util.List;
+import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
+
/**
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
@@ -87,9 +88,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
private CheckLongPressHelper mLongPressHelper;
private StylusEventHelper mStylusEventHelper;
- private static final int DROP_IN_ANIMATION_DURATION = 400;
- private static final int INITIAL_ITEM_ANIMATION_DURATION = 350;
- private static final int FINAL_ITEM_ANIMATION_DURATION = 200;
+ static final int DROP_IN_ANIMATION_DURATION = 400;
// Flag whether the folder should open itself when an item is dragged over is enabled.
public static final boolean SPRING_LOADING_ENABLED = true;
@@ -99,27 +98,19 @@ public class FolderIcon extends FrameLayout implements FolderListener {
@Thunk BubbleTextView mFolderName;
- // These variables are all associated with the drawing of the preview; they are stored
- // as member variables for shared usage and to avoid computation on each frame
- private float mIntrinsicIconSize = -1;
- private int mTotalWidth = -1;
- private int mPrevTopPadding = -1;
-
PreviewBackground mBackground = new PreviewBackground();
private boolean mBackgroundIsVisible = true;
- private PreviewLayoutRule mPreviewLayoutRule;
+ FolderIconPreviewVerifier mPreviewVerifier;
+ PreviewLayoutRule mPreviewLayoutRule;
+ private PreviewItemManager mPreviewItemManager;
+ private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
boolean mAnimating = false;
private Rect mTempBounds = new Rect();
private float mSlop;
- FolderIconPreviewVerifier mPreviewVerifier;
- private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
- private ArrayList<PreviewItemDrawingParams> mDrawingParams = new ArrayList<>();
- private Drawable mReferenceDrawable = null;
-
private Alarm mOpenAlarm = new Alarm();
private FolderBadgeInfo mBadgeInfo = new FolderBadgeInfo();
@@ -158,6 +149,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
new StackFolderIconLayoutRule() :
new ClippedFolderIconLayoutRule();
mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ mPreviewItemManager = new PreviewItemManager(this);
}
public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
@@ -213,7 +205,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
private void setFolder(Folder folder) {
mFolder = folder;
mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
- updateItemDrawingParams(false);
+ mPreviewItemManager.updateItemDrawingParams(false);
}
private boolean willAcceptItem(ItemInfo item) {
@@ -229,7 +221,15 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
public void addItem(ShortcutInfo item) {
- mInfo.add(item, true);
+ addItem(item, true);
+ }
+
+ public void addItem(ShortcutInfo item, boolean animate) {
+ mInfo.add(item, animate);
+ }
+
+ public void removeItem(ShortcutInfo item, boolean animate) {
+ mInfo.remove(item, animate);
}
public void onDragEnter(ItemInfo dragInfo) {
@@ -254,40 +254,28 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
};
- public Drawable prepareCreate(final View destView) {
- Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
- computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
- destView.getMeasuredWidth());
- return animateDrawable;
+ public Drawable prepareCreateAnimation(final View destView) {
+ return mPreviewItemManager.prepareCreateAnimation(destView);
}
public void performCreateAnimation(final ShortcutInfo destInfo, final View destView,
final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect,
float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {
-
- // These correspond two the drawable and view that the icon was dropped _onto_
- Drawable animateDrawable = prepareCreate(destView);
-
- mReferenceDrawable = animateDrawable;
-
+ prepareCreateAnimation(destView);
addItem(destInfo);
// This will animate the first item from it's position as an icon into its
// position as the first item in the preview
- animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION, false, null);
+ mPreviewItemManager.createFirstItemAnimation(false /* reverse */, null)
+ .start();
// This will animate the dragView (srcView) into the new folder
onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable);
}
- public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
- Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1];
- computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
- finalView.getMeasuredWidth());
-
- // This will animate the first item from it's position as an icon into its
- // position as the first item in the preview
- animateFirstItem(animateDrawable, FINAL_ITEM_ANIMATION_DURATION, true,
- onCompleteRunnable);
+ public void performDestroyAnimation(Runnable onCompleteRunnable) {
+ // This will animate the final item in the preview to be full size.
+ mPreviewItemManager.createFirstItemAnimation(true /* reverse */, onCompleteRunnable)
+ .start();
}
public void onDragExit() {
@@ -324,13 +312,39 @@ public class FolderIcon extends FrameLayout implements FolderListener {
workspace.resetTransitionTransform((CellLayout) getParent().getParent());
}
+ boolean itemAdded = false;
+ if (index >= mPreviewLayoutRule.maxNumItems()
+ && mPreviewLayoutRule.hasEnterExitIndices()) {
+ List<BubbleTextView> oldPreviewItems = getPreviewItemsOnPage(0);
+ addItem(item, false);
+ List<BubbleTextView> newPreviewItems = getPreviewItemsOnPage(0);
+
+ if (!oldPreviewItems.containsAll(newPreviewItems)) {
+ for (int i = 0; i < newPreviewItems.size(); ++i) {
+ if (newPreviewItems.get(i).getTag().equals(item)) {
+ // If the item dropped is going to be in the preview, we update the
+ // index here to reflect its position in the preview.
+ index = i;
+ }
+ }
+ mPreviewItemManager.onDrop(oldPreviewItems, newPreviewItems, item);
+ itemAdded = true;
+ } else {
+ removeItem(item, false);
+ }
+ }
+
+ if (!itemAdded) {
+ addItem(item);
+ }
+
int[] center = new int[2];
float scale = getLocalCenterForIndex(index, index + 1, center);
center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
to.offset(center[0] - animateView.getMeasuredWidth() / 2,
- center[1] - animateView.getMeasuredHeight() / 2);
+ center[1] - animateView.getMeasuredHeight() / 2);
float finalAlpha = index < mPreviewLayoutRule.maxNumItems() ? 0.5f : 0f;
@@ -339,15 +353,14 @@ public class FolderIcon extends FrameLayout implements FolderListener {
1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
new DecelerateInterpolator(2), new AccelerateInterpolator(2),
postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
- addItem(item);
+
mFolder.hideItem(item);
- final PreviewItemDrawingParams params = index < mDrawingParams.size() ?
- mDrawingParams.get(index) : null;
- if (params != null) params.hidden = true;
+ if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
+ final int finalIndex = index;
postDelayed(new Runnable() {
public void run() {
- if (params != null) params.hidden = false;
+ mPreviewItemManager.hidePreviewItem(finalIndex, false);
mFolder.showItem(item);
invalidate();
}
@@ -369,25 +382,6 @@ public class FolderIcon extends FrameLayout implements FolderListener {
onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable);
}
- private void computePreviewDrawingParams(int drawableSize, int totalSize) {
- if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize ||
- mPrevTopPadding != getPaddingTop()) {
- mIntrinsicIconSize = drawableSize;
- mTotalWidth = totalSize;
- mPrevTopPadding = getPaddingTop();
-
- mBackground.setup(mLauncher, this, mTotalWidth, getPaddingTop());
- mPreviewLayoutRule.init(mBackground.previewSize, mIntrinsicIconSize,
- Utilities.isRtl(getResources()));
-
- updateItemDrawingParams(false);
- }
- }
-
- private void computePreviewDrawingParams(Drawable d) {
- computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth());
- }
-
public void setBadgeInfo(FolderBadgeInfo badgeInfo) {
updateBadgeScale(mBadgeInfo.hasBadge(), badgeInfo.hasBadge());
mBadgeInfo = badgeInfo;
@@ -421,56 +415,21 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
private float getLocalCenterForIndex(int index, int curNumItems, int[] center) {
- mTmpParams = computePreviewItemDrawingParams(
+ mTmpParams = mPreviewItemManager.computePreviewItemDrawingParams(
Math.min(mPreviewLayoutRule.maxNumItems(), index), curNumItems, mTmpParams);
mTmpParams.transX += mBackground.basePreviewOffsetX;
mTmpParams.transY += mBackground.basePreviewOffsetY;
- float offsetX = mTmpParams.transX + (mTmpParams.scale * mIntrinsicIconSize) / 2;
- float offsetY = mTmpParams.transY + (mTmpParams.scale * mIntrinsicIconSize) / 2;
+
+ float intrinsicIconSize = mPreviewItemManager.getIntrinsicIconSize();
+ float offsetX = mTmpParams.transX + (mTmpParams.scale * intrinsicIconSize) / 2;
+ float offsetY = mTmpParams.transY + (mTmpParams.scale * intrinsicIconSize) / 2;
center[0] = Math.round(offsetX);
center[1] = Math.round(offsetY);
return mTmpParams.scale;
}
- PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
- PreviewItemDrawingParams params) {
- // We use an index of -1 to represent an icon on the workspace for the destroy and
- // create animations
- if (index == -1) {
- return getFinalIconParams(params);
- }
- return mPreviewLayoutRule.computePreviewItemDrawingParams(index, curNumItems, params);
- }
-
- private PreviewItemDrawingParams getFinalIconParams(PreviewItemDrawingParams params) {
- float iconSize = mLauncher.getDeviceProfile().iconSizePx;
-
- final float scale = iconSize / mReferenceDrawable.getIntrinsicWidth();
- final float trans = (mBackground.previewSize - iconSize) / 2;
-
- params.update(trans, trans, scale);
- return params;
- }
-
- private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
- canvas.save(Canvas.MATRIX_SAVE_FLAG);
- canvas.translate(params.transX, params.transY);
- canvas.scale(params.scale, params.scale);
- Drawable d = params.drawable;
-
- if (d != null) {
- Rect bounds = d.getBounds();
- canvas.save();
- canvas.translate(-bounds.left, -bounds.top);
- canvas.scale(mIntrinsicIconSize / bounds.width(), mIntrinsicIconSize / bounds.height());
- d.draw(canvas);
- canvas.restore();
- }
- canvas.restore();
- }
-
public void setFolderBackground(PreviewBackground bg) {
mBackground = bg;
mBackground.setInvalidateDelegate(this);
@@ -487,9 +446,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
if (!mBackgroundIsVisible) return;
- if (mReferenceDrawable != null) {
- computePreviewDrawingParams(mReferenceDrawable);
- }
+ mPreviewItemManager.recomputePreviewDrawingParams();
if (!mBackground.drawingDelegated()) {
mBackground.drawBackground(canvas);
@@ -512,14 +469,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
// The items are drawn in coordinates relative to the preview offset
canvas.translate(mBackground.basePreviewOffsetX, mBackground.basePreviewOffsetY);
-
- // The first item should be drawn last (ie. on top of later items)
- for (int i = mDrawingParams.size() - 1; i >= 0; i--) {
- PreviewItemDrawingParams p = mDrawingParams.get(i);
- if (!p.hidden) {
- drawPreviewItem(canvas, p);
- }
- }
+ mPreviewItemManager.draw(canvas);
canvas.translate(-mBackground.basePreviewOffsetX, -mBackground.basePreviewOffsetY);
if (mPreviewLayoutRule.clipToBackground() && canvas.isHardwareAccelerated()) {
@@ -546,19 +496,6 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
}
- private void animateFirstItem(final Drawable d, int duration, final boolean reverse,
- final Runnable onCompleteRunnable) {
- FolderPreviewItemAnim anim;
- if (!reverse) {
- anim = new FolderPreviewItemAnim(this, mDrawingParams.get(0), -1, -1, 0, 2, duration,
- onCompleteRunnable);
- } else {
- anim = new FolderPreviewItemAnim(this, mDrawingParams.get(0), 0, 2, -1, -1, duration,
- onCompleteRunnable);
- }
- anim.start();
- }
-
public void setTextVisible(boolean visible) {
if (visible) {
mFolderName.setVisibility(VISIBLE);
@@ -571,15 +508,25 @@ public class FolderIcon extends FrameLayout implements FolderListener {
return mFolderName.getVisibility() == VISIBLE;
}
- public List<BubbleTextView> getItemsToDisplay() {
+ /**
+ * Returns the list of preview items displayed in the icon.
+ */
+ public List<BubbleTextView> getPreviewItems() {
+ return getPreviewItemsOnPage(0);
+ }
+
+ /**
+ * Returns the list of "preview items" on {@param page}.
+ */
+ public List<BubbleTextView> getPreviewItemsOnPage(int page) {
mPreviewVerifier.setFolderInfo(mFolder.getInfo());
List<BubbleTextView> itemsToDisplay = new ArrayList<>();
- List<View> allItems = mFolder.getItemsInRankOrder();
- int numItems = allItems.size();
+ List<BubbleTextView> itemsOnPage = mFolder.getItemsOnPage(page);
+ int numItems = itemsOnPage.size();
for (int rank = 0; rank < numItems; ++rank) {
- if (mPreviewVerifier.isItemInPreview(rank)) {
- itemsToDisplay.add((BubbleTextView) allItems.get(rank));
+ if (mPreviewVerifier.isItemInPreview(page, rank)) {
+ itemsToDisplay.add(itemsOnPage.get(rank));
}
if (itemsToDisplay.size() == FolderIcon.NUM_ITEMS_IN_PREVIEW) {
@@ -591,63 +538,12 @@ public class FolderIcon extends FrameLayout implements FolderListener {
@Override
protected boolean verifyDrawable(@NonNull Drawable who) {
- for (int i = 0; i < mDrawingParams.size(); i++) {
- if (mDrawingParams.get(i).drawable == who) {
- return true;
- }
- }
- return super.verifyDrawable(who);
- }
-
- private void updateItemDrawingParams(boolean animate) {
- List<BubbleTextView> items = getItemsToDisplay();
- int nItemsInPreview = items.size();
-
- int prevNumItems = mDrawingParams.size();
-
- // We adjust the size of the list to match the number of items in the preview
- while (nItemsInPreview < mDrawingParams.size()) {
- mDrawingParams.remove(mDrawingParams.size() - 1);
- }
- while (nItemsInPreview > mDrawingParams.size()) {
- mDrawingParams.add(new PreviewItemDrawingParams(0, 0, 0, 0));
- }
-
- for (int i = 0; i < mDrawingParams.size(); i++) {
- PreviewItemDrawingParams p = mDrawingParams.get(i);
- p.drawable = items.get(i).getCompoundDrawables()[1];
-
- if (p.drawable != null && !mFolder.isOpen()) {
- // Set the callback to FolderIcon as it is responsible to drawing the icon. The
- // callback will be release when the folder is opened.
- p.drawable.setCallback(this);
- }
-
- if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) {
- computePreviewItemDrawingParams(i, nItemsInPreview, p);
- if (mReferenceDrawable == null) {
- mReferenceDrawable = p.drawable;
- }
- } else {
- FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, i, prevNumItems, i,
- nItemsInPreview, DROP_IN_ANIMATION_DURATION, null);
-
- if (p.anim != null) {
- if (p.anim.hasEqualFinalState(anim)) {
- // do nothing, let the current animation finish
- continue;
- }
- p.anim.cancel();
- }
- p.anim = anim;
- p.anim.start();
- }
- }
+ return mPreviewItemManager.verifyDrawable(who) || super.verifyDrawable(who);
}
@Override
public void onItemsChanged(boolean animate) {
- updateItemDrawingParams(animate);
+ mPreviewItemManager.updateItemDrawingParams(animate);
invalidate();
requestLayout();
}
@@ -790,13 +686,22 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
}
+ public void onFolderClose(int currentPage) {
+ mPreviewItemManager.onFolderClose(currentPage);
+ }
+
interface PreviewLayoutRule {
PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
- PreviewItemDrawingParams params);
+ PreviewItemDrawingParams params);
void init(int availableSpace, float intrinsicIconSize, boolean rtl);
float scaleForItem(int index, int totalNumItems);
float getIconSize();
int maxNumItems();
boolean clipToBackground();
+
+ boolean hasEnterExitIndices();
+ int getExitIndex();
+ int getEnterIndex();
+
}
}
diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
index d0d8e79d5..d054a5d42 100644
--- a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
+++ b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
@@ -25,15 +25,51 @@ import com.android.launcher3.config.FeatureFlags;
*/
public class FolderIconPreviewVerifier {
+ private final int mMaxGridCountX;
+ private final int mMaxGridCountY;
+ private final int mMaxItemsPerPage;
+ private final int[] mGridSize = new int[2];
+
+ private int mGridCountX;
+ private boolean mDisplayingUpperLeftQuadrant = false;
+
public FolderIconPreviewVerifier(InvariantDeviceProfile profile) {
- // b/37570804
+ mMaxGridCountX = profile.numFolderColumns;
+ mMaxGridCountY = profile.numFolderRows;
+ mMaxItemsPerPage = mMaxGridCountX * mMaxGridCountY;
}
public void setFolderInfo(FolderInfo info) {
- // b/37570804
+ int numItemsInFolder = info.contents.size();
+ FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX,
+ mMaxGridCountY, mMaxItemsPerPage, mGridSize);
+ mGridCountX = mGridSize[0];
+
+ mDisplayingUpperLeftQuadrant = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION
+ && !FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON
+ && numItemsInFolder > FolderIcon.NUM_ITEMS_IN_PREVIEW;
}
+ /**
+ * Returns whether the item with {@param rank} is in the default Folder icon preview.
+ */
public boolean isItemInPreview(int rank) {
+ return isItemInPreview(0, rank);
+ }
+
+ /**
+ * @param page The page the item is on.
+ * @param rank The rank of the item.
+ * @return True iff the icon is in the 2x2 upper left quadrant of the Folder.
+ */
+ public boolean isItemInPreview(int page, int rank) {
+ // First page items are laid out such that the first 4 items are always in the upper
+ // left quadrant. For all other pages, we need to check the row and col.
+ if (page > 0 || mDisplayingUpperLeftQuadrant) {
+ int col = rank % mGridCountX;
+ int row = rank / mGridCountX;
+ return col < 2 && row < 2;
+ }
return rank < FolderIcon.NUM_ITEMS_IN_PREVIEW;
}
}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 33ac5baf2..f4ac0a140 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -201,29 +201,18 @@ public class FolderPagedView extends PagedView {
}
public void allocateSpaceForRank(int rank) {
- ArrayList<View> views = new ArrayList<>(mFolder.getItemsInRankOrder());
+ ArrayList<View> views = new ArrayList<>(mFolder.getItemsInReadingOrder());
views.add(rank, null);
arrangeChildren(views, views.size(), false);
}
- private ArrayList<View> createListWithViewAtPos(ArrayList<View> list, View v, int position) {
- ArrayList<View> newList = new ArrayList<>(list.size() + 1);
- newList.addAll(list);
- newList.add(position, v);
- return newList;
- }
-
/**
- * Create space for a new item and returns the rank for that item.
+ * Create space for a new item at the end, and returns the rank for that item.
* Also sets the current page to the last page.
*/
public int allocateRankForNewItem() {
- ArrayList<View> rankOrder = mFolder.getItemsInRankOrder();
- int rank = rankOrder.size();
-
- ArrayList<View> views = createListWithViewAtPos(rankOrder, null, rank);
- arrangeChildren(views, views.size(), false);
-
+ int rank = getItemCount();
+ allocateSpaceForRank(rank);
setCurrentPage(rank / mMaxItemsPerPage);
return rank;
}
@@ -240,59 +229,20 @@ public class FolderPagedView extends PagedView {
* related attributes. It assumes that {@param item} is already attached to the view.
*/
public void addViewForRank(View view, ShortcutInfo item, int rank) {
- updateShortcutInfoWithRank(item, rank);
-
- ArrayList<View> views = createListWithViewAtPos(mFolder.getItemsInRankOrder(), view, rank);
- arrangeChildren(views, views.size(), false);
- }
+ int pagePos = rank % mMaxItemsPerPage;
+ int pageNo = rank / mMaxItemsPerPage;
- /**
- * Similar to {@link #addViewForRank(View, ShortcutInfo, int)}}, but specific to real time
- * reorder.
- *
- * The difference here is that during real time reorder, we are moving the Views in a contained
- * order.
- */
- public void addViewForRankDuringReorder(View view, ShortcutInfo item, int rank) {
- updateShortcutInfoWithRank(item, rank);
+ item.rank = rank;
+ item.cellX = pagePos % mGridCountX;
+ item.cellY = pagePos / mGridCountX;
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
lp.cellX = item.cellX;
lp.cellY = item.cellY;
-
- int pageNo = rank / mMaxItemsPerPage;
getPageAt(pageNo).addViewToCellLayout(
view, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true);
}
- /**
- * Similar to {@link #addViewForRank(View, ShortcutInfo, int)}, but specific to drag and drop.
- *
- * The difference is that we handle the drag and drop by adjusting the reading order of the
- * children, rather than based on their rank.
- */
- public void addViewForRankDuringDragAndDrop(View view, ShortcutInfo item, int readingRank) {
- updateShortcutInfoWithRank(item, readingRank);
-
- ArrayList<View> views = createListWithViewAtPos(mFolder.getItemsInReadingOrder(), view,
- readingRank);
-
- int numItems = views.size();
- ArrayList<View> rankOrder = new ArrayList<>(numItems);
- for (int i = 0; i < numItems; ++i) {
- rankOrder.add(views.get(getReadingOrderPosForRank(i)));
- }
-
- arrangeChildren(rankOrder, numItems, false);
- }
-
- private void updateShortcutInfoWithRank(ShortcutInfo info, int rank) {
- info.rank = rank;
- getCellXYPositionForRank(rank, sTmpArray);
- info.cellX = sTmpArray[0];
- info.cellY = sTmpArray[1];
- }
-
@SuppressLint("InflateParams")
public View createNewView(ShortcutInfo item) {
final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
@@ -360,19 +310,18 @@ public class FolderPagedView extends PagedView {
* It essentially removes all views from all the pages and then adds them again in appropriate
* page.
*
- * @param rankOrderedList the rank-ordered list of children.
+ * @param list the ordered list of children.
* @param itemCount if greater than the total children count, empty spaces are left
* at the end, otherwise it is ignored.
*
*/
- public void arrangeChildren(ArrayList<View> rankOrderedList, int itemCount) {
- arrangeChildren(rankOrderedList, itemCount, true);
+ public void arrangeChildren(ArrayList<View> list, int itemCount) {
+ arrangeChildren(list, itemCount, true);
}
@SuppressLint("RtlHardcoded")
- private void arrangeChildren(ArrayList<View> rankOrderedList, int itemCount,
- boolean saveChanges) {
- ArrayList<CellLayout> pages = new ArrayList<CellLayout>();
+ private void arrangeChildren(ArrayList<View> list, int itemCount, boolean saveChanges) {
+ ArrayList<CellLayout> pages = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) {
CellLayout page = (CellLayout) getChildAt(i);
page.removeAllViews();
@@ -390,7 +339,7 @@ public class FolderPagedView extends PagedView {
Launcher.getLauncher(getContext()).getDeviceProfile().inv);
rank = 0;
for (int i = 0; i < itemCount; i++) {
- View v = rankOrderedList.size() > i ? rankOrderedList.get(i) : null;
+ View v = list.size() > i ? list.get(i) : null;
if (currentPage == null || position >= mMaxItemsPerPage) {
// Next page
if (pageItr.hasNext()) {
@@ -403,10 +352,8 @@ public class FolderPagedView extends PagedView {
if (v != null) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
- getCellXYPositionForRank(rank, sTmpArray);
- newX = sTmpArray[0];
- newY = sTmpArray[1];
-
+ newX = position % mGridCountX;
+ newY = position / mGridCountX;
ItemInfo info = (ItemInfo) v.getTag();
if (info.cellX != newX || info.cellY != newY || info.rank != rank) {
info.cellX = newX;
@@ -704,7 +651,7 @@ public class FolderPagedView extends PagedView {
if (v != null) {
if (pageToAnimate != p) {
page.removeView(v);
- addViewForRankDuringReorder(v, (ShortcutInfo) v.getTag(), moveStart);
+ addViewForRank(v, (ShortcutInfo) v.getTag(), moveStart);
} else {
// Do a fake animation before removing it.
final int newRank = moveStart;
@@ -717,14 +664,14 @@ public class FolderPagedView extends PagedView {
mPendingAnimations.remove(v);
v.setTranslationX(oldTranslateX);
((CellLayout) v.getParent().getParent()).removeView(v);
- addViewForRankDuringReorder(v, (ShortcutInfo) v.getTag(), newRank);
+ addViewForRank(v, (ShortcutInfo) v.getTag(), newRank);
}
};
v.animate()
- .translationXBy((direction > 0 ^ mIsRtl) ? -v.getWidth() : v.getWidth())
- .setDuration(REORDER_ANIMATION_DURATION)
- .setStartDelay(0)
- .withEndAction(endAction);
+ .translationXBy((direction > 0 ^ mIsRtl) ? -v.getWidth() : v.getWidth())
+ .setDuration(REORDER_ANIMATION_DURATION)
+ .setStartDelay(0)
+ .withEndAction(endAction);
mPendingAnimations.put(v, endAction);
}
}
@@ -754,102 +701,4 @@ public class FolderPagedView extends PagedView {
public int itemsPerPage() {
return mMaxItemsPerPage;
}
-
- public int getReadingOrderPosForRank(int rank) {
- return getReadingOrderPosForRank(rank, mMaxItemsPerPage, mGridCountX, sTmpArray);
- }
-
- /**
- * Returns the reading order position for a given rank.
- *
- * ie. For the permutation below, rank 0 returns 0, rank 1 returns 1, rank 4 returns 2,
- * rank 2 returns 3, rank 3 returns 4, rank 5 returns 5.
- *
- * R0 R1 R4
- * R2 R3 R5
- *
- * @param outXY If notnull, we also return the cell X/Y position.
- */
- public static int getReadingOrderPosForRank(int rank, int maxItemsPerPage, int gridX,
- int[] outXY) {
- outXY = outXY == null ? sTmpArray : outXY;
- getCellXYPositionForRank(rank, maxItemsPerPage, gridX, outXY);
-
- if (rank >= maxItemsPerPage) {
- return rank;
- }
-
- return outXY[0] + (gridX * outXY[1]);
- }
-
- public void getCellXYPositionForRank(int rank, int[] outXY) {
- getCellXYPositionForRank(rank, mMaxItemsPerPage, mGridCountX, outXY);
- }
-
- /**
- * Returns the cell XY position for a Folder item with the given rank.
- */
- public static void getCellXYPositionForRank(int rank, int maxItemsPerPage, int gridX,
- int[] outXY) {
- boolean onFirstPage = rank < maxItemsPerPage;
-
- if (onFirstPage && gridX == 3) {
- outXY[0] = FolderPermutation.THREE_COLS[rank][0];
- outXY[1] = FolderPermutation.THREE_COLS[rank][1];
- } else if (onFirstPage && gridX == 4) {
- outXY[0] = FolderPermutation.FOUR_COLS[rank][0];
- outXY[1] = FolderPermutation.FOUR_COLS[rank][1];
- } else if (onFirstPage && gridX == 5) {
- outXY[0] = FolderPermutation.FIVE_COLS[rank][0];
- outXY[1] = FolderPermutation.FIVE_COLS[rank][1];
- } else {
- outXY[0] = (rank % maxItemsPerPage) % gridX;
- outXY[1] = (rank % maxItemsPerPage) / gridX;
- }
- }
-
- /**
- * Provides the mapping between a folder item's rank and its cell location, based on the
- * number of columns.
- *
- * We use this mapping, rather than the regular reading order, to preserve the items in the
- * upper left quadrant of the Folder. This allows a smooth transition between the FolderIcon
- * and the opened Folder.
- *
- * TODO: We will replace these hard coded tables with an algorithm b/62986680
- */
- private static class FolderPermutation {
- /**
- * R0 R1 R4
- * R2 R3 R5
- * R6 R7 R8
- */
- static final int[][] THREE_COLS = new int[][] {
- {0, 0}, {1, 0}, {0, 1}, {1, 1}, {2, 0}, {2, 1}, {0, 2}, {1, 2}, {2, 2}
- };
-
- /**
- * R0 R1 R4 R6
- * R2 R3 R5 R7
- * R8 R9 R10 R11
- * R12 R13 R14 R15
- */
- static final int[][] FOUR_COLS = new int[][] {
- {0, 0}, {1, 0}, {0, 1}, {1, 1}, {2, 0}, {2, 1}, {3, 0}, {3, 1}, {0, 2}, {1, 2},
- {2, 2}, {3, 2}, {0, 3}, {1, 3}, {2, 3}, {3, 3}
- };
-
- /**
- * R0 R1 R4 R6 R12
- * R2 R3 R5 R7 R13
- * R8 R9 R10 R11 R14
- * R15 R16 R17 R18 R19
- * R20 R21 R22 R23 R24
- */
- static final int[][] FIVE_COLS = new int[][] {
- {0, 0}, {1, 0}, {0, 1}, {1, 1}, {2, 0}, {2, 1}, {3, 0}, {3, 1}, {0, 2}, {1, 2},
- {2, 2}, {3, 2}, {4, 0}, {4, 1}, {4, 2}, {0, 3}, {1, 3}, {2, 3}, {3, 3}, {4, 3},
- {0, 4}, {1, 4}, {2, 4}, {3, 4}, {4, 4}
- };
- }
}
diff --git a/src/com/android/launcher3/folder/FolderPreviewItemAnim.java b/src/com/android/launcher3/folder/FolderPreviewItemAnim.java
index 0da7c5cb1..be075bc9a 100644
--- a/src/com/android/launcher3/folder/FolderPreviewItemAnim.java
+++ b/src/com/android/launcher3/folder/FolderPreviewItemAnim.java
@@ -25,16 +25,16 @@ import com.android.launcher3.LauncherAnimUtils;
* Animates a Folder preview item.
*/
class FolderPreviewItemAnim {
+
+ private static PreviewItemDrawingParams sTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
+
private ValueAnimator mValueAnimator;
float finalScale;
float finalTransX;
float finalTransY;
- private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
-
/**
- * @param folderIcon The FolderIcon this preview will be drawn in.
* @param params layout params to animate
* @param index0 original index of the item to be animated
* @param items0 original number of items in the preview
@@ -43,20 +43,20 @@ class FolderPreviewItemAnim {
* @param duration duration in ms of the animation
* @param onCompleteRunnable runnable to execute upon animation completion
*/
- FolderPreviewItemAnim(final FolderIcon folderIcon, final PreviewItemDrawingParams params,
- int index0, int items0, int index1, int items1, int duration,
- final Runnable onCompleteRunnable) {
- folderIcon.computePreviewItemDrawingParams(index1, items1, mTmpParams);
+ FolderPreviewItemAnim(final PreviewItemManager previewItemManager,
+ final PreviewItemDrawingParams params, int index0, int items0, int index1, int items1,
+ int duration, final Runnable onCompleteRunnable) {
+ previewItemManager.computePreviewItemDrawingParams(index1, items1, sTmpParams);
- finalScale = mTmpParams.scale;
- finalTransX = mTmpParams.transX;
- finalTransY = mTmpParams.transY;
+ finalScale = sTmpParams.scale;
+ finalTransX = sTmpParams.transX;
+ finalTransY = sTmpParams.transY;
- folderIcon.computePreviewItemDrawingParams(index0, items0, mTmpParams);
+ previewItemManager.computePreviewItemDrawingParams(index0, items0, sTmpParams);
- final float scale0 = mTmpParams.scale;
- final float transX0 = mTmpParams.transX;
- final float transY0 = mTmpParams.transY;
+ final float scale0 = sTmpParams.scale;
+ final float transX0 = sTmpParams.transX;
+ final float transY0 = sTmpParams.transY;
mValueAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
@@ -66,7 +66,7 @@ class FolderPreviewItemAnim {
params.transX = transX0 + progress * (finalTransX - transX0);
params.transY = transY0 + progress * (finalTransY - transY0);
params.scale = scale0 + progress * (finalScale - scale0);
- folderIcon.invalidate();
+ previewItemManager.onParamsChanged();
}
});
mValueAnimator.addListener(new AnimatorListenerAdapter() {
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
new file mode 100644
index 000000000..2ecb54ca8
--- /dev/null
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2017 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.folder;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION;
+
+/**
+ * Manages the drawing and animations of {@link PreviewItemDrawingParams} for a {@link FolderIcon}.
+ */
+public class PreviewItemManager {
+
+ private FolderIcon mIcon;
+
+ // These variables are all associated with the drawing of the preview; they are stored
+ // as member variables for shared usage and to avoid computation on each frame
+ private float mIntrinsicIconSize = -1;
+ private int mTotalWidth = -1;
+ private int mPrevTopPadding = -1;
+ private Drawable mReferenceDrawable = null;
+
+ // These hold the first page preview items
+ private ArrayList<PreviewItemDrawingParams> mFirstPageParams = new ArrayList<>();
+ // These hold the current page preview items. It is empty if the current page is the first page.
+ private ArrayList<PreviewItemDrawingParams> mCurrentPageParams = new ArrayList<>();
+
+ private float mCurrentPageItemsTransX = 0;
+ private boolean mShouldSlideInFirstPage;
+
+ static final int INITIAL_ITEM_ANIMATION_DURATION = 350;
+ private static final int FINAL_ITEM_ANIMATION_DURATION = 200;
+
+ private static final int SLIDE_IN_FIRST_PAGE_ANIMATION_DURATION_DELAY = 100;
+ private static final int SLIDE_IN_FIRST_PAGE_ANIMATION_DURATION = 300;
+ private static final int ITEM_SLIDE_IN_OUT_DISTANCE_PX = 200;
+
+ public PreviewItemManager(FolderIcon icon) {
+ mIcon = icon;
+ }
+
+ /**
+ * @param reverse If true, animates the final item in the preview to be full size. If false,
+ * animates the first item to its position in the preview.
+ */
+ public FolderPreviewItemAnim createFirstItemAnimation(final boolean reverse,
+ final Runnable onCompleteRunnable) {
+ return reverse
+ ? new FolderPreviewItemAnim(this, mFirstPageParams.get(0), 0, 2, -1, -1,
+ FINAL_ITEM_ANIMATION_DURATION, onCompleteRunnable)
+ : new FolderPreviewItemAnim(this, mFirstPageParams.get(0), -1, -1, 0, 2,
+ INITIAL_ITEM_ANIMATION_DURATION, onCompleteRunnable);
+ }
+
+ Drawable prepareCreateAnimation(final View destView) {
+ Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
+ computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
+ destView.getMeasuredWidth());
+ mReferenceDrawable = animateDrawable;
+ return animateDrawable;
+ }
+
+ public void recomputePreviewDrawingParams() {
+ if (mReferenceDrawable != null) {
+ computePreviewDrawingParams(mReferenceDrawable.getIntrinsicWidth(),
+ mIcon.getMeasuredWidth());
+ }
+ }
+
+ private void computePreviewDrawingParams(int drawableSize, int totalSize) {
+ if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize ||
+ mPrevTopPadding != mIcon.getPaddingTop()) {
+ mIntrinsicIconSize = drawableSize;
+ mTotalWidth = totalSize;
+ mPrevTopPadding = mIcon.getPaddingTop();
+
+ mIcon.mBackground.setup(mIcon.mLauncher, mIcon, mTotalWidth, mIcon.getPaddingTop());
+ mIcon.mPreviewLayoutRule.init(mIcon.mBackground.previewSize, mIntrinsicIconSize,
+ Utilities.isRtl(mIcon.getResources()));
+
+ updateItemDrawingParams(false);
+ }
+ }
+
+ PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
+ PreviewItemDrawingParams params) {
+ // We use an index of -1 to represent an icon on the workspace for the destroy and
+ // create animations
+ if (index == -1) {
+ return getFinalIconParams(params);
+ }
+ return mIcon.mPreviewLayoutRule.computePreviewItemDrawingParams(index, curNumItems, params);
+ }
+
+ private PreviewItemDrawingParams getFinalIconParams(PreviewItemDrawingParams params) {
+ float iconSize = mIcon.mLauncher.getDeviceProfile().iconSizePx;
+
+ final float scale = iconSize / mReferenceDrawable.getIntrinsicWidth();
+ final float trans = (mIcon.mBackground.previewSize - iconSize) / 2;
+
+ params.update(trans, trans, scale);
+ return params;
+ }
+
+ public void drawParams(Canvas canvas, ArrayList<PreviewItemDrawingParams> params,
+ float transX) {
+ canvas.translate(transX, 0);
+ // The first item should be drawn last (ie. on top of later items)
+ for (int i = params.size() - 1; i >= 0; i--) {
+ PreviewItemDrawingParams p = params.get(i);
+ if (!p.hidden) {
+ drawPreviewItem(canvas, p);
+ }
+ }
+ canvas.translate(-transX, 0);
+ }
+
+ public void draw(Canvas canvas) {
+ float firstPageItemsTransX = 0;
+ if (mShouldSlideInFirstPage) {
+ drawParams(canvas, mCurrentPageParams, mCurrentPageItemsTransX);
+
+ firstPageItemsTransX = -ITEM_SLIDE_IN_OUT_DISTANCE_PX + mCurrentPageItemsTransX;
+ }
+
+ drawParams(canvas, mFirstPageParams, firstPageItemsTransX);
+ }
+
+ public void onParamsChanged() {
+ mIcon.invalidate();
+ }
+
+ private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.translate(params.transX, params.transY);
+ canvas.scale(params.scale, params.scale);
+ Drawable d = params.drawable;
+
+ if (d != null) {
+ Rect bounds = d.getBounds();
+ canvas.save();
+ canvas.translate(-bounds.left, -bounds.top);
+ canvas.scale(mIntrinsicIconSize / bounds.width(), mIntrinsicIconSize / bounds.height());
+ d.draw(canvas);
+ canvas.restore();
+ }
+ canvas.restore();
+ }
+
+ public void hidePreviewItem(int index, boolean hidden) {
+ PreviewItemDrawingParams params = index < mFirstPageParams.size() ?
+ mFirstPageParams.get(index) : null;
+ if (params != null) {
+ params.hidden = hidden;
+ }
+ }
+
+ void buildParamsForPage(int page, ArrayList<PreviewItemDrawingParams> params, boolean animate) {
+ List<BubbleTextView> items = mIcon.getPreviewItemsOnPage(page);
+ int prevNumItems = params.size();
+
+ // We adjust the size of the list to match the number of items in the preview.
+ while (items.size() < params.size()) {
+ params.remove(params.size() - 1);
+ }
+ while (items.size() > params.size()) {
+ params.add(new PreviewItemDrawingParams(0, 0, 0, 0));
+ }
+
+ int numItemsInFirstPagePreview = page == 0 ? items.size() : FolderIcon.NUM_ITEMS_IN_PREVIEW;
+ for (int i = 0; i < params.size(); i++) {
+ PreviewItemDrawingParams p = params.get(i);
+ p.drawable = items.get(i).getCompoundDrawables()[1];
+
+ if (p.drawable != null && !mIcon.mFolder.isOpen()) {
+ // Set the callback to FolderIcon as it is responsible to drawing the icon. The
+ // callback will be released when the folder is opened.
+ p.drawable.setCallback(mIcon);
+ }
+
+ if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) {
+ computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, p);
+ if (mReferenceDrawable == null) {
+ mReferenceDrawable = p.drawable;
+ }
+ } else {
+ FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, i, prevNumItems, i,
+ numItemsInFirstPagePreview, DROP_IN_ANIMATION_DURATION, null);
+
+ if (p.anim != null) {
+ if (p.anim.hasEqualFinalState(anim)) {
+ // do nothing, let the current animation finish
+ continue;
+ }
+ p.anim.cancel();
+ }
+ p.anim = anim;
+ p.anim.start();
+ }
+ }
+ }
+
+ void onFolderClose(int currentPage) {
+ // If we are not closing on the first page, we animate the current page preview items
+ // out, and animate the first page preview items in.
+ mShouldSlideInFirstPage = currentPage != 0;
+ if (mShouldSlideInFirstPage) {
+ mCurrentPageItemsTransX = 0;
+ buildParamsForPage(currentPage, mCurrentPageParams, false);
+ onParamsChanged();
+
+ ValueAnimator slideAnimator = ValueAnimator.ofFloat(0, ITEM_SLIDE_IN_OUT_DISTANCE_PX);
+ slideAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ mCurrentPageItemsTransX = (float) valueAnimator.getAnimatedValue();
+ onParamsChanged();
+ }
+ });
+ slideAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentPageParams.clear();
+ }
+ });
+ slideAnimator.setStartDelay(SLIDE_IN_FIRST_PAGE_ANIMATION_DURATION_DELAY);
+ slideAnimator.setDuration(SLIDE_IN_FIRST_PAGE_ANIMATION_DURATION);
+ slideAnimator.start();
+ }
+ }
+
+ void updateItemDrawingParams(boolean animate) {
+ buildParamsForPage(0, mFirstPageParams, animate);
+ }
+
+ boolean verifyDrawable(@NonNull Drawable who) {
+ for (int i = 0; i < mFirstPageParams.size(); i++) {
+ if (mFirstPageParams.get(i).drawable == who) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ float getIntrinsicIconSize() {
+ return mIntrinsicIconSize;
+ }
+
+ /**
+ * Handles the case where items in the preview are either:
+ * - Moving into the preview
+ * - Moving into a new position
+ * - Moving out of the preview
+ *
+ * @param oldParams The list of items in the old preview.
+ * @param newParams The list of items in the new preview.
+ * @param dropped The item that was dropped onto the FolderIcon.
+ */
+ public void onDrop(List<BubbleTextView> oldParams, List<BubbleTextView> newParams,
+ ShortcutInfo dropped) {
+ int numItems = newParams.size();
+ final ArrayList<PreviewItemDrawingParams> params = mFirstPageParams;
+ buildParamsForPage(0, params, false);
+
+ // New preview items for items that are moving in (except for the dropped item).
+ List<BubbleTextView> moveIn = new ArrayList<>();
+ for (BubbleTextView btv : newParams) {
+ if (!oldParams.contains(btv) && !btv.getTag().equals(dropped)) {
+ moveIn.add(btv);
+ }
+ }
+ for (int i = 0; i < moveIn.size(); ++i) {
+ int prevIndex = newParams.indexOf(moveIn.get(i));
+ PreviewItemDrawingParams p = params.get(prevIndex);
+ computePreviewItemDrawingParams(prevIndex, numItems, p);
+ updateTransitionParam(p, moveIn.get(i), mIcon.mPreviewLayoutRule.getEnterIndex(),
+ newParams.indexOf(moveIn.get(i)));
+ }
+
+ // Items that are moving into new positions within the preview.
+ for (int newIndex = 0; newIndex < newParams.size(); ++newIndex) {
+ int oldIndex = oldParams.indexOf(newParams.get(newIndex));
+ if (oldIndex >= 0 && newIndex != oldIndex) {
+ PreviewItemDrawingParams p = params.get(newIndex);
+ updateTransitionParam(p, newParams.get(newIndex), oldIndex, newIndex);
+ }
+ }
+
+ // Old preview items that need to be moved out.
+ List<BubbleTextView> moveOut = new ArrayList<>(oldParams);
+ moveOut.removeAll(newParams);
+ for (int i = 0; i < moveOut.size(); ++i) {
+ BubbleTextView item = moveOut.get(i);
+ int oldIndex = oldParams.indexOf(item);
+ PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null);
+ updateTransitionParam(p, item, oldIndex, mIcon.mPreviewLayoutRule.getExitIndex());
+ params.add(0, p); // We want these items first so that they are on drawn last.
+ }
+
+ for (int i = 0; i < params.size(); ++i) {
+ if (params.get(i).anim != null) {
+ params.get(i).anim.start();
+ }
+ }
+ }
+
+ private void updateTransitionParam(final PreviewItemDrawingParams p, BubbleTextView btv,
+ int prevIndex, int newIndex) {
+ p.drawable = btv.getCompoundDrawables()[1];
+ if (!mIcon.mFolder.isOpen()) {
+ // Set the callback to FolderIcon as it is responsible to drawing the icon. The
+ // callback will be released when the folder is opened.
+ p.drawable.setCallback(mIcon);
+ }
+
+ FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, prevIndex,
+ FolderIcon.NUM_ITEMS_IN_PREVIEW, newIndex, FolderIcon.NUM_ITEMS_IN_PREVIEW,
+ DROP_IN_ANIMATION_DURATION, null);
+ if (p.anim != null && !p.anim.hasEqualFinalState(anim)) {
+ p.anim.cancel();
+ }
+ p.anim = anim;
+ }
+}
diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
index 138dc1c55..7d10556d0 100644
--- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
@@ -97,4 +97,19 @@ public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
public boolean clipToBackground() {
return false;
}
+
+ @Override
+ public boolean hasEnterExitIndices() {
+ return false;
+ }
+
+ @Override
+ public int getExitIndex() {
+ throw new RuntimeException("hasEnterExitIndices not supported");
+ }
+
+ @Override
+ public int getEnterIndex() {
+ throw new RuntimeException("hasEnterExitIndices not supported");
+ }
}
diff --git a/src/com/android/launcher3/graphics/GradientView.java b/src/com/android/launcher3/graphics/GradientView.java
index 9dd950454..678396df1 100644
--- a/src/com/android/launcher3/graphics/GradientView.java
+++ b/src/com/android/launcher3/graphics/GradientView.java
@@ -18,13 +18,14 @@ package com.android.launcher3.graphics;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader;
+import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
@@ -34,6 +35,7 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dynamicui.WallpaperColorInfo;
+import com.android.launcher3.util.Themes;
/**
* Draws a translucent radial gradient background from an initial state with progress 0.0 to a
@@ -42,44 +44,44 @@ import com.android.launcher3.dynamicui.WallpaperColorInfo;
public class GradientView extends View implements WallpaperColorInfo.OnChangeListener {
private static final int DEFAULT_COLOR = Color.WHITE;
- private static final float GRADIENT_ALPHA_MASK_LENGTH_DP = 300;
+ private static final int ALPHA_MASK_HEIGHT_DP = 500;
+ private static final int ALPHA_MASK_WIDTH_DP = 2;
+ private static final int ALPHA_COLORS = 0xBF;
private static final boolean DEBUG = false;
- private final Bitmap mFinalGradientMask;
private final Bitmap mAlphaGradientMask;
+ private boolean mShowScrim = true;
private int mColor1 = DEFAULT_COLOR;
private int mColor2 = DEFAULT_COLOR;
private int mWidth;
private int mHeight;
private final RectF mAlphaMaskRect = new RectF();
private final RectF mFinalMaskRect = new RectF();
- private final Paint mPaint = new Paint();
+ private final Paint mPaintWithScrim = new Paint();
+ private final Paint mPaintNoScrim = new Paint();
private float mProgress;
- private final int mMaskHeight;
+ private final int mMaskHeight, mMaskWidth;
private final Context mAppContext;
private final Paint mDebugPaint = DEBUG ? new Paint() : null;
private final Interpolator mAccelerator = new AccelerateInterpolator();
private final float mAlphaStart;
private final WallpaperColorInfo mWallpaperColorInfo;
+ private final int mScrimColor;
public GradientView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mAppContext = context.getApplicationContext();
- this.mMaskHeight = Utilities.pxFromDp(GRADIENT_ALPHA_MASK_LENGTH_DP,
+ this.mMaskHeight = Utilities.pxFromDp(ALPHA_MASK_HEIGHT_DP,
+ mAppContext.getResources().getDisplayMetrics());
+ this.mMaskWidth = Utilities.pxFromDp(ALPHA_MASK_WIDTH_DP,
mAppContext.getResources().getDisplayMetrics());
Launcher launcher = Launcher.getLauncher(context);
this.mAlphaStart = launcher.getDeviceProfile().isVerticalBarLayout() ? 0 : 100;
+ this.mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
this.mWallpaperColorInfo = WallpaperColorInfo.getInstance(launcher);
updateColors();
-
- int finalAlpha = 0xBF;
- mFinalGradientMask = Utilities.convertToAlphaMask(
- Utilities.createOnePixBitmap(), finalAlpha);
- Bitmap alphaMaskFromResource = BitmapFactory.decodeResource(context.getResources(),
- R.drawable.all_apps_alpha_mask);
- mAlphaGradientMask = Utilities.convertToAlphaMask(
- alphaMaskFromResource, finalAlpha);
+ mAlphaGradientMask = createDitheredAlphaMask();
}
@Override
@@ -101,8 +103,10 @@ public class GradientView extends View implements WallpaperColorInfo.OnChangeLis
}
private void updateColors() {
- this.mColor1 = mWallpaperColorInfo.getMainColor();
- this.mColor2 = mWallpaperColorInfo.getSecondaryColor();
+ this.mColor1 = ColorUtils.setAlphaComponent(mWallpaperColorInfo.getMainColor(),
+ ALPHA_COLORS);
+ this.mColor2 = ColorUtils.setAlphaComponent(mWallpaperColorInfo.getSecondaryColor(),
+ ALPHA_COLORS);
if (mWidth + mHeight > 0) {
createRadialShader();
}
@@ -122,34 +126,53 @@ public class GradientView extends View implements WallpaperColorInfo.OnChangeLis
private void createRadialShader() {
final float gradientCenterY = 1.05f;
float radius = Math.max(mHeight, mWidth) * gradientCenterY;
-
float posScreenBottom = (radius - mHeight) / radius; // center lives below screen
- RadialGradient shader = new RadialGradient(
+
+ RadialGradient shaderNoScrim = new RadialGradient(
mWidth * 0.5f,
mHeight * gradientCenterY,
radius,
new int[] {mColor1, mColor1, mColor2},
new float[] {0f, posScreenBottom, 1f},
Shader.TileMode.CLAMP);
- mPaint.setShader(shader);
+ mPaintNoScrim.setShader(shaderNoScrim);
+
+ int color1 = ColorUtils.compositeColors(mScrimColor,mColor1);
+ int color2 = ColorUtils.compositeColors(mScrimColor,mColor2);
+ RadialGradient shaderWithScrim = new RadialGradient(
+ mWidth * 0.5f,
+ mHeight * gradientCenterY,
+ radius,
+ new int[] { color1, color1, color2 },
+ new float[] {0f, posScreenBottom, 1f},
+ Shader.TileMode.CLAMP);
+ mPaintWithScrim.setShader(shaderWithScrim);
}
public void setProgress(float progress) {
+ setProgress(progress, true);
+ }
+
+ public void setProgress(float progress, boolean showScrim) {
this.mProgress = progress;
+ this.mShowScrim = showScrim;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
+ Paint paint = mShowScrim ? mPaintWithScrim : mPaintNoScrim;
+
float head = 0.29f;
float linearProgress = head + (mProgress * (1f - head));
float startMaskY = (1f - linearProgress) * mHeight - mMaskHeight * linearProgress;
float interpolatedAlpha = (255 - mAlphaStart) * mAccelerator.getInterpolation(mProgress);
- mPaint.setAlpha((int) (mAlphaStart + interpolatedAlpha));
- mAlphaMaskRect.set(0, startMaskY, mWidth, startMaskY + mMaskHeight);
- mFinalMaskRect.set(0, startMaskY + mMaskHeight, mWidth, mHeight);
- canvas.drawBitmap(mAlphaGradientMask, null, mAlphaMaskRect, mPaint);
- canvas.drawBitmap(mFinalGradientMask, null, mFinalMaskRect, mPaint);
+ paint.setAlpha((int) (mAlphaStart + interpolatedAlpha));
+ float div = (float) Math.floor(startMaskY + mMaskHeight);
+ mAlphaMaskRect.set(0, startMaskY, mWidth, div);
+ mFinalMaskRect.set(0, div, mWidth, mHeight);
+ canvas.drawBitmap(mAlphaGradientMask, null, mAlphaMaskRect, paint);
+ canvas.drawRect(mFinalMaskRect, paint);
if (DEBUG) {
mDebugPaint.setColor(0xFF00FF00);
@@ -157,4 +180,20 @@ public class GradientView extends View implements WallpaperColorInfo.OnChangeLis
canvas.drawLine(0, startMaskY + mMaskHeight, mWidth, startMaskY + mMaskHeight, mDebugPaint);
}
}
+
+ public Bitmap createDitheredAlphaMask() {
+ Bitmap dst = Bitmap.createBitmap(mMaskWidth, mMaskHeight, Bitmap.Config.ALPHA_8);
+ Canvas c = new Canvas(dst);
+ Paint paint = new Paint(Paint.DITHER_FLAG);
+ LinearGradient lg = new LinearGradient(0, 0, 0, mMaskHeight,
+ new int[]{
+ 0x00FFFFFF,
+ ColorUtils.setAlphaComponent(Color.WHITE, (int) (0xFF * 0.95)),
+ 0xFFFFFFFF},
+ new float[]{0f, 0.8f, 1f},
+ Shader.TileMode.CLAMP);
+ paint.setShader(lg);
+ c.drawRect(0, 0, mMaskWidth, mMaskHeight, paint);
+ return dst;
+ }
} \ No newline at end of file
diff --git a/src/com/android/launcher3/graphics/ScrimView.java b/src/com/android/launcher3/graphics/ScrimView.java
deleted file mode 100644
index 6d1f30a41..000000000
--- a/src/com/android/launcher3/graphics/ScrimView.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.graphics;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.support.v4.graphics.ColorUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Interpolator;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.Themes;
-
-public class ScrimView extends View {
-
- private static final boolean DEBUG = false;
-
- private static final int MASK_HEIGHT_DP = 300;
- private static final float MASK_START_LENGTH_FACTOR = 1f;
- private static final boolean APPLY_ALPHA = true;
-
- private final Bitmap mFinalScrimMask;
- private final Bitmap mAlphaScrimMask;
-
- private final int mMaskHeight;
- private int mVisibleHeight;
- private final int mHeadStart;
-
- private final RectF mAlphaMaskRect = new RectF();
- private final RectF mFinalMaskRect = new RectF();
- private final Paint mPaint = new Paint();
- private float mProgress;
- private final Interpolator mAccelerator = new AccelerateInterpolator();
- private final Paint mDebugPaint = DEBUG ? new Paint() : null;
- private final int mAlphaStart;
-
- public ScrimView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mMaskHeight = Utilities.pxFromDp(MASK_HEIGHT_DP, getResources().getDisplayMetrics());
- mHeadStart = (int) (mMaskHeight * MASK_START_LENGTH_FACTOR);
- mAlphaStart = Launcher.getLauncher(context)
- .getDeviceProfile().isVerticalBarLayout() ? 0 : 55;
-
- int scrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
- int scrimAlpha = Color.alpha(scrimColor);
- mPaint.setColor(scrimColor);
- mFinalScrimMask = Utilities.convertToAlphaMask(
- Utilities.createOnePixBitmap(), scrimAlpha);
- Bitmap alphaMaskFromResource = BitmapFactory.decodeResource(getResources(),
- R.drawable.all_apps_alpha_mask);
- mAlphaScrimMask = Utilities.convertToAlphaMask(alphaMaskFromResource, scrimAlpha);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int width = MeasureSpec.getSize(widthMeasureSpec);
- mVisibleHeight = MeasureSpec.getSize(heightMeasureSpec);
- setMeasuredDimension(width, mVisibleHeight * 2);
- setProgress(mProgress);
- }
-
- public void setProgress(float progress) {
- mProgress = progress;
- float initialY = mVisibleHeight - mHeadStart;
- float fullTranslationY = mVisibleHeight;
- float linTranslationY = initialY - progress * fullTranslationY;
- setTranslationY(linTranslationY);
-
- if (APPLY_ALPHA) {
- int alpha = mAlphaStart + (int) ((255f - mAlphaStart)
- * mAccelerator.getInterpolation(progress));
- mPaint.setAlpha(alpha);
- invalidate();
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- mAlphaMaskRect.set(0, 0, getWidth(), mMaskHeight);
- mFinalMaskRect.set(0, mMaskHeight, getWidth(), getHeight());
- canvas.drawBitmap(mAlphaScrimMask, null, mAlphaMaskRect, mPaint);
- canvas.drawBitmap(mFinalScrimMask, null, mFinalMaskRect, mPaint);
-
- if (DEBUG) {
- mDebugPaint.setColor(0xFF0000FF);
- canvas.drawLine(0, mAlphaMaskRect.top, getWidth(), mAlphaMaskRect.top, mDebugPaint);
- canvas.drawLine(0, mAlphaMaskRect.bottom, getWidth(), mAlphaMaskRect.bottom, mDebugPaint);
- }
- }
-
-}
diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/graphics/ShadowGenerator.java
index fffea8e3e..60eeef5df 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/graphics/ShadowGenerator.java
@@ -53,27 +53,38 @@ public class ShadowGenerator {
private final Canvas mCanvas;
private final Paint mBlurPaint;
private final Paint mDrawPaint;
+ private final BlurMaskFilter mDefaultBlurMaskFilter;
private ShadowGenerator(Context context) {
mIconSize = LauncherAppState.getIDP(context).iconBitmapSize;
mCanvas = new Canvas();
mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
- mBlurPaint.setMaskFilter(new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL));
mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL);
}
public synchronized Bitmap recreateIcon(Bitmap icon) {
+ return recreateIcon(icon, true, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA,
+ KEY_SHADOW_ALPHA);
+ }
+
+ public synchronized Bitmap recreateIcon(Bitmap icon, boolean resize,
+ BlurMaskFilter blurMaskFilter, int ambientAlpha, int keyAlpha) {
+ int width = resize ? mIconSize : icon.getWidth();
+ int height = resize ? mIconSize : icon.getHeight();
int[] offset = new int[2];
+
+ mBlurPaint.setMaskFilter(blurMaskFilter);
Bitmap shadow = icon.extractAlpha(mBlurPaint, offset);
- Bitmap result = Bitmap.createBitmap(mIconSize, mIconSize, Config.ARGB_8888);
+ Bitmap result = Bitmap.createBitmap(width, height, Config.ARGB_8888);
mCanvas.setBitmap(result);
// Draw ambient shadow
- mDrawPaint.setAlpha(AMBIENT_SHADOW_ALPHA);
+ mDrawPaint.setAlpha(ambientAlpha);
mCanvas.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);
// Draw key shadow
- mDrawPaint.setAlpha(KEY_SHADOW_ALPHA);
+ mDrawPaint.setAlpha(keyAlpha);
mCanvas.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint);
// Draw the icon
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index edbb88c93..d5c6515c0 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -20,6 +20,7 @@ import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.util.Log;
@@ -35,11 +36,10 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.LogConfig;
-import java.util.List;
import java.util.Locale;
+import java.util.UUID;
import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
@@ -62,13 +62,21 @@ public class UserEventDispatcher {
private static final String TAG = "UserEvent";
private static final boolean IS_VERBOSE =
FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT);
+ private static final String UUID_STORAGE = "uuid";
public static UserEventDispatcher newInstance(Context context, boolean isInLandscapeMode,
boolean isInMultiWindowMode) {
+ SharedPreferences sharedPrefs = Utilities.getDevicePrefs(context);
+ String uuidStr = sharedPrefs.getString(UUID_STORAGE, null);
+ if (uuidStr == null) {
+ uuidStr = UUID.randomUUID().toString();
+ sharedPrefs.edit().putString(UUID_STORAGE, uuidStr).apply();
+ }
UserEventDispatcher ued = Utilities.getOverrideObject(UserEventDispatcher.class,
context.getApplicationContext(), R.string.user_event_dispatcher_class);
ued.mIsInLandscapeMode = isInLandscapeMode;
ued.mIsInMultiWindowMode = isInMultiWindowMode;
+ ued.mUuidStr = uuidStr;
return ued;
}
@@ -116,9 +124,7 @@ public class UserEventDispatcher {
private long mActionDurationMillis;
private boolean mIsInMultiWindowMode;
private boolean mIsInLandscapeMode;
-
- // Used for filling in predictedRank on {@link Target}s.
- private List<ComponentKey> mPredictedApps;
+ private String mUuidStr;
// APP_ICON SHORTCUT WIDGET
// --------------------------------------------------------------
@@ -127,32 +133,11 @@ public class UserEventDispatcher {
// intentHash required
// --------------------------------------------------------------
- protected LauncherEvent createLauncherEvent(View v, int intentHashCode, ComponentName cn) {
- LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
- newItemTarget(v), newTarget(Target.Type.CONTAINER));
-
- // TODO: make idx percolate up the view hierarchy if needed.
- int idx = 0;
- if (fillInLogContainerData(event, v)) {
- ItemInfo itemInfo = (ItemInfo) v.getTag();
- event.srcTarget[idx].intentHash = intentHashCode;
- if (cn != null) {
- event.srcTarget[idx].packageNameHash = cn.getPackageName().hashCode();
- event.srcTarget[idx].componentHash = cn.hashCode();
- if (mPredictedApps != null) {
- event.srcTarget[idx].predictedRank = mPredictedApps.indexOf(
- new ComponentKey(cn, itemInfo.user));
- }
- }
- }
- return event;
- }
-
/**
* Fills in the container data on the given event if the given view is not null.
* @return whether container data was added.
*/
- private boolean fillInLogContainerData(LauncherEvent event, @Nullable View v) {
+ protected boolean fillInLogContainerData(LauncherEvent event, @Nullable View v) {
// Fill in grid(x,y), pageIndex of the child and container type of the parent
LogContainerProvider provider = getLaunchProviderRecursive(v);
if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
@@ -164,20 +149,31 @@ public class UserEventDispatcher {
}
public void logAppLaunch(View v, Intent intent) {
- LauncherEvent ev = createLauncherEvent(v, intent.hashCode(), intent.getComponent());
- if (ev == null) {
- return;
+ LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
+ newItemTarget(v), newTarget(Target.Type.CONTAINER));
+
+ if (fillInLogContainerData(event, v)) {
+ fillIntentInfo(event.srcTarget[0], intent);
+ }
+ dispatchUserEvent(event, intent);
+ }
+
+ protected void fillIntentInfo(Target target, Intent intent) {
+ target.intentHash = intent.hashCode();
+ ComponentName cn = intent.getComponent();
+ if (cn != null) {
+ target.packageNameHash = (mUuidStr + cn.getPackageName()).hashCode();
+ target.componentHash = (mUuidStr + cn.flattenToString()).hashCode();
}
- dispatchUserEvent(ev, intent);
}
public void logNotificationLaunch(View v, PendingIntent intent) {
- ComponentName dummyComponent = new ComponentName(intent.getCreatorPackage(), "--dummy--");
- LauncherEvent ev = createLauncherEvent(v, intent.hashCode(), dummyComponent);
- if (ev == null) {
- return;
+ LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
+ newItemTarget(v), newTarget(Target.Type.CONTAINER));
+ if (fillInLogContainerData(event, v)) {
+ event.srcTarget[0].packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode();
}
- dispatchUserEvent(ev, null);
+ dispatchUserEvent(event, null);
}
public void logActionCommand(int command, int containerType) {
@@ -262,10 +258,6 @@ public class UserEventDispatcher {
resetElapsedContainerMillis();
}
- public void setPredictedApps(List<ComponentKey> predictedApps) {
- mPredictedApps = predictedApps;
- }
-
/* Currently we are only interested in whether this event happens or not and don't
* care about which screen moves to where. */
public void logOverviewReorder() {
@@ -337,7 +329,10 @@ public class UserEventDispatcher {
}
private static String getTargetsStr(Target[] targets) {
- return "child:" + LoggerUtils.getTargetStr(targets[0]) +
- (targets.length > 1 ? "\tparent:" + LoggerUtils.getTargetStr(targets[1]) : "");
+ String result = "child:" + LoggerUtils.getTargetStr(targets[0]);
+ for (int i = 1; i < targets.length; i++) {
+ result += "\tparent:" + LoggerUtils.getTargetStr(targets[i]);
+ }
+ return result;
}
}
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index b83c9b95d..2455eabea 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -206,7 +206,10 @@ public class NotificationFooterLayout extends FrameLayout {
@Override
public void onAnimationEnd(Animator animation) {
((ViewGroup) getParent()).findViewById(R.id.divider).setVisibility(GONE);
- ((ViewGroup) getParent()).removeView(NotificationFooterLayout.this);
+ // Keep view around because gutter is aligned to it, but remove height to
+ // both hide the view and keep calculations correct for last dismissal.
+ getLayoutParams().height = 0;
+ requestLayout();
}
});
collapseFooter.start();
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 0cd5a4c40..11f6aa081 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -17,6 +17,8 @@
package com.android.launcher3.notification;
import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.app.Notification;
import android.content.Context;
import android.graphics.Rect;
@@ -28,7 +30,9 @@ import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
+import com.android.launcher3.anim.PropertyResetListener;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
@@ -89,6 +93,8 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
}
public Animator animateHeightRemoval(int heightToRemove, boolean shouldRemoveFromTop) {
+ AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+
Rect startRect = new Rect(mPillRect);
Rect endRect = new Rect(mPillRect);
if (shouldRemoveFromTop) {
@@ -96,8 +102,18 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
} else {
endRect.bottom -= heightToRemove;
}
- return new RoundedRectRevealOutlineProvider(getBackgroundRadius(), getBackgroundRadius(),
- startRect, endRect, mRoundedCorners).createRevealAnimator(this, false);
+ anim.play(new RoundedRectRevealOutlineProvider(getBackgroundRadius(), getBackgroundRadius(),
+ startRect, endRect, mRoundedCorners).createRevealAnimator(this, false));
+
+ View bottomGutter = findViewById(R.id.gutter_bottom);
+ if (bottomGutter != null && bottomGutter.getVisibility() == VISIBLE) {
+ Animator translateGutter = ObjectAnimator.ofFloat(bottomGutter, TRANSLATION_Y,
+ -heightToRemove);
+ translateGutter.addListener(new PropertyResetListener<>(TRANSLATION_Y, 0f));
+ anim.play(translateGutter);
+ }
+
+ return anim;
}
public void updateHeader(int notificationCount, @Nullable IconPalette palette) {
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 8121fd55f..73d89aa18 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -28,6 +28,7 @@ import android.service.notification.StatusBarNotification;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Pair;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.config.FeatureFlags;
@@ -47,6 +48,8 @@ import java.util.Set;
@TargetApi(Build.VERSION_CODES.O)
public class NotificationListener extends NotificationListenerService {
+ public static final String TAG = "NotificationListener";
+
private static final int MSG_NOTIFICATION_POSTED = 1;
private static final int MSG_NOTIFICATION_REMOVED = 2;
private static final int MSG_NOTIFICATION_FULL_REFRESH = 3;
@@ -71,9 +74,19 @@ public class NotificationListener extends NotificationListenerService {
mUiHandler.obtainMessage(message.what, message.obj).sendToTarget();
break;
case MSG_NOTIFICATION_FULL_REFRESH:
- final List<StatusBarNotification> activeNotifications = sIsConnected
- ? filterNotifications(getActiveNotifications())
- : new ArrayList<StatusBarNotification>();
+ List<StatusBarNotification> activeNotifications;
+ if (sIsConnected) {
+ try {
+ activeNotifications = filterNotifications(getActiveNotifications());
+ } catch (SecurityException ex) {
+ Log.e(TAG, "SecurityException: failed to fetch notifications");
+ activeNotifications = new ArrayList<StatusBarNotification>();
+
+ }
+ } else {
+ activeNotifications = new ArrayList<StatusBarNotification>();
+ }
+
mUiHandler.obtainMessage(message.what, activeNotifications).sendToTarget();
break;
}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java b/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java
index 8bcb9794a..682d5a967 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java
@@ -57,7 +57,7 @@ public class PageIndicatorCaretLandscape extends PageIndicator {
protected void onDraw(Canvas canvas) {
Rect drawableBounds = getCaretDrawable().getBounds();
int count = canvas.save();
- canvas.translate(getWidth() - drawableBounds.width(),
+ canvas.translate((getWidth() - drawableBounds.width()) / 2,
getHeight() - drawableBounds.height());
getCaretDrawable().draw(canvas);
canvas.restoreToCount(count);
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java b/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java
index 6b992fc22..29834d764 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java
@@ -21,7 +21,9 @@ import android.widget.ImageView;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dynamicui.ExtractedColors;
+import com.android.launcher3.dynamicui.WallpaperColorInfo;
/**
* A PageIndicator that briefly shows a fraction of a line when moving between pages.
@@ -128,6 +130,10 @@ public class PageIndicatorLineCaret extends PageIndicator {
mLauncher = Launcher.getLauncher(context);
mLineHeight = res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_line_height);
setCaretDrawable(new CaretDrawable(context));
+
+ boolean darkText = WallpaperColorInfo.getInstance(context).supportsDarkText();
+ mActiveAlpha = darkText ? BLACK_ALPHA : WHITE_ALPHA;
+ mLinePaint.setColor(darkText ? Color.BLACK : Color.WHITE);
}
@Override
@@ -219,6 +225,9 @@ public class PageIndicatorLineCaret extends PageIndicator {
* - mostly opaque black if the hotseat is black (ignoring alpha)
*/
public void updateColor(ExtractedColors extractedColors) {
+ if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) {
+ return;
+ }
int originalLineAlpha = mLinePaint.getAlpha();
int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX);
if (color != Color.TRANSPARENT) {
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 3de9bad4c..c3e2d8b89 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -399,6 +399,29 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
openAnim.start();
}
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ enforceContainedWithinScreen(l, r);
+
+ }
+
+ private void enforceContainedWithinScreen(int left, int right) {
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ if (getTranslationX() + left < 0 ||
+ getTranslationX() + right > dragLayer.getWidth()) {
+ // If we are still off screen, center horizontally too.
+ mGravity |= Gravity.CENTER_HORIZONTAL;
+ }
+
+ if (Gravity.isHorizontal(mGravity)) {
+ setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2);
+ }
+ if (Gravity.isVertical(mGravity)) {
+ setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2);
+ }
+ }
+
/**
* Returns the point at which the center of the arrow merges with the first popup item.
*/
@@ -517,21 +540,8 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
mIsAboveIcon = true;
}
- if (x < dragLayer.getLeft() || x + width > dragLayer.getRight()) {
- // If we are still off screen, center horizontally too.
- mGravity |= Gravity.CENTER_HORIZONTAL;
- }
-
- if (Gravity.isHorizontal(mGravity)) {
- setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2);
- } else {
- setX(x);
- }
- if (Gravity.isVertical(mGravity)) {
- setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2);
- } else {
- setY(y);
- }
+ setX(x);
+ setY(y);
}
private boolean isAlignedWithStart() {
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 00e2644a5..523016008 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -134,7 +134,7 @@ public class RestoreDbTask {
}
public static void setPending(Context context, boolean isPending) {
- FileLog.d(TAG, "Restore data received through full backup");
+ FileLog.d(TAG, "Restore data received through full backup " + isPending);
Utilities.getPrefs(context).edit().putBoolean(RESTORE_TASK_PENDING, isPending).commit();
}
}
diff --git a/src/com/android/launcher3/views/ButtonPreference.java b/src/com/android/launcher3/views/ButtonPreference.java
new file mode 100644
index 000000000..4697e25e4
--- /dev/null
+++ b/src/com/android/launcher3/views/ButtonPreference.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 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.views;
+
+import android.content.Context;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Extension of {@link Preference} which makes the widget layout clickable.
+ *
+ * @see #setWidgetLayoutResource(int)
+ */
+public class ButtonPreference extends Preference {
+
+ private View.OnClickListener mClickListener;
+
+ public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public ButtonPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ButtonPreference(Context context) {
+ super(context);
+ }
+
+ public void setButtonOnClickListener(View.OnClickListener clickListener) {
+ if (mClickListener != clickListener) {
+ mClickListener = clickListener;
+ notifyChanged();
+ }
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+
+ ViewGroup widgetFrame = view.findViewById(android.R.id.widget_frame);
+ if (widgetFrame != null) {
+ View button = widgetFrame.getChildAt(0);
+ if (button != null) {
+ button.setOnClickListener(mClickListener);
+ }
+ widgetFrame.setVisibility(
+ (mClickListener == null || button == null) ? View.GONE : View.VISIBLE);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
index c0b5fe156..c8203f7f2 100644
--- a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
+++ b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
@@ -23,6 +23,7 @@ import android.graphics.Color;
import android.graphics.Region;
import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
+import android.widget.TextView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
@@ -50,13 +51,12 @@ public class DoubleShadowBubbleTextView extends BubbleTextView {
@Override
public void onDraw(Canvas canvas) {
- // If text is transparent, don't draw any shadow
- int alpha = Color.alpha(getCurrentTextColor());
- if (alpha == 0) {
- getPaint().clearShadowLayer();
+ // If text is transparent or shadow alpha is 0, don't draw any shadow
+ if (mShadowInfo.skipDoubleShadow(this)) {
super.onDraw(canvas);
return;
}
+ int alpha = Color.alpha(getCurrentTextColor());
// We enhance the shadow by drawing the shadow twice
getPaint().setShadowLayer(mShadowInfo.ambientShadowBlur, 0, 0,
@@ -97,5 +97,25 @@ public class DoubleShadowBubbleTextView extends BubbleTextView {
keyShadowColor = a.getColor(R.styleable.ShadowInfo_keyShadowColor, 0);
a.recycle();
}
+
+ public boolean skipDoubleShadow(TextView textView) {
+ int textAlpha = Color.alpha(textView.getCurrentTextColor());
+ int keyShadowAlpha = Color.alpha(keyShadowColor);
+ int ambientShadowAlpha = Color.alpha(ambientShadowColor);
+ if (textAlpha == 0 || (keyShadowAlpha == 0 && ambientShadowAlpha == 0)) {
+ textView.getPaint().clearShadowLayer();
+ return true;
+ } else if (ambientShadowAlpha > 0) {
+ textView.getPaint().setShadowLayer(ambientShadowBlur, 0, 0,
+ ColorUtils.setAlphaComponent(ambientShadowColor, textAlpha));
+ return true;
+ } else if (keyShadowAlpha > 0) {
+ textView.getPaint().setShadowLayer(keyShadowBlur, 0.0f, keyShadowOffset,
+ ColorUtils.setAlphaComponent(keyShadowColor, textAlpha));
+ return true;
+ } else {
+ return false;
+ }
+ }
}
}
diff --git a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
index e8f13a13c..62b6903f7 100644
--- a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.widget;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.compat.ShortcutConfigActivityInfo;
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index a754375ae..b2fb09157 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -276,7 +276,9 @@ public class WidgetsBottomSheet extends AbstractFloatingView implements Insettab
public void setTranslationY(float translationY) {
super.setTranslationY(translationY);
if (mGradientBackground == null) return;
- mGradientBackground.setProgress((mTranslationYClosed - translationY) / mTranslationYRange);
+ float p = (mTranslationYClosed - translationY) / mTranslationYRange;
+ boolean showScrim = p <= 0;
+ mGradientBackground.setProgress(p, showScrim);
}
@Override