diff options
Diffstat (limited to 'src')
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 |