diff options
28 files changed, 555 insertions, 222 deletions
diff --git a/go/res/values-v26/bools.xml b/go/res/values-v26/bools.xml new file mode 100644 index 000000000..cc4a7ba44 --- /dev/null +++ b/go/res/values-v26/bools.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* Copyright 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. +*/ +--> + +<resources> + <bool name="notification_badging_enabled">false</bool> +</resources>
\ No newline at end of file diff --git a/go/res/values/override.xml b/go/res/values/override.xml new file mode 100644 index 000000000..268cb980c --- /dev/null +++ b/go/res/values/override.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +* 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. +*/ +--> +<resources> + <!-- String representing the intent to delete a package. --> + <string name="delete_package_intent" translatable="false">#Intent;action=android.intent.action.DELETE;launchFlags=0x10800000;B.android.intent.extra.RETURN_RESULT=true;end</string> +</resources>
\ No newline at end of file diff --git a/res/values/config.xml b/res/values/config.xml index a62947ec7..b41172bb1 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -19,6 +19,9 @@ q=<query> to the data to the intent --> <string name="market_search_intent" translatable="false">market://search?c=apps</string> + <!-- String representing the intent to delete a package.--> + <string name="delete_package_intent" translatable="false">#Intent;action=android.intent.action.DELETE;launchFlags=0x10800000;end</string> + <!-- Values for icon shape overrides. These should correspond to entries defined in icon_shape_override_paths_names --> <string-array translatable="false" name="icon_shape_override_paths_values"> diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index 1a405f934..a486a3aa3 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -55,7 +55,6 @@ public class AppWidgetResizeFrame extends FrameLayout private final int[] mDirectionVector = new int[2]; private final int[] mLastDirectionVector = new int[2]; - private final int[] mTmpPt = new int[2]; private final IntRange mTempRange1 = new IntRange(); private final IntRange mTempRange2 = new IntRange(); @@ -344,13 +343,12 @@ public class AppWidgetResizeFrame extends FrameLayout return rect; } - /** - * This is the final step of the resize. Here we save the new widget size and position - * to LauncherModel and animate the resize frame. - */ - public void commitResize() { + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + // We are done with resizing the widget. Save the widget size & position to LauncherModel resizeWidgetIfNeeded(true); - requestLayout(); } private void onTouchUp() { diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java index c55a5860e..82175b721 100644 --- a/src/com/android/launcher3/BaseContainerView.java +++ b/src/com/android/launcher3/BaseContainerView.java @@ -62,7 +62,7 @@ public abstract class BaseContainerView extends FrameLayout public BaseContainerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && this instanceof AllAppsContainerView) { + if (this instanceof AllAppsContainerView) { mBaseDrawable = new ColorDrawable(); } else { TypedArray a = context.obtainStyledAttributes(attrs, diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index e229d6c65..8492a7985 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1310,9 +1310,7 @@ public class Launcher extends BaseActivity mDragController.addDropTarget(mWorkspace); mDropTargetBar.setup(mDragController); - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { - mAllAppsController.setupViews(mAppsView, mHotseat, mWorkspace); - } + mAllAppsController.setupViews(mAppsView, mHotseat, mWorkspace); if (TestingUtils.MEMORY_DUMP_ENABLED) { TestingUtils.addWeightWatcher(this); @@ -2280,7 +2278,7 @@ public class Launcher extends BaseActivity if (v instanceof FolderIcon) { onClickFolderIcon(v); } - } else if ((FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && v instanceof PageIndicator) || + } else if ((v instanceof PageIndicator) || (v == mAllAppsButton && mAllAppsButton != null)) { onClickAllAppsButton(v); } else if (tag instanceof AppInfo) { @@ -3763,16 +3761,12 @@ public class Launcher extends BaseActivity * Implementation of the method from LauncherModel.Callbacks. * * @param updated list of shortcuts which have changed. - * @param removed list of shortcuts which were deleted in the background. This can happen when - * an app gets removed from the system or some of its components are no longer - * available. */ @Override - public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated, - final ArrayList<ShortcutInfo> removed, final UserHandle user) { + public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated, final UserHandle user) { Runnable r = new Runnable() { public void run() { - bindShortcutsChanged(updated, removed, user); + bindShortcutsChanged(updated, user); } }; if (waitUntilResume(r)) { @@ -3782,31 +3776,6 @@ public class Launcher extends BaseActivity if (!updated.isEmpty()) { mWorkspace.updateShortcuts(updated); } - - if (!removed.isEmpty()) { - HashSet<ComponentName> removedComponents = new HashSet<>(); - HashSet<ShortcutKey> removedDeepShortcuts = new HashSet<>(); - - for (ShortcutInfo si : removed) { - if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) { - removedDeepShortcuts.add(ShortcutKey.fromItemInfo(si)); - } else { - removedComponents.add(si.getTargetComponent()); - } - } - - if (!removedComponents.isEmpty()) { - ItemInfoMatcher matcher = ItemInfoMatcher.ofComponents(removedComponents, user); - mWorkspace.removeItemsByMatcher(matcher); - mDragController.onAppsRemoved(matcher); - } - - if (!removedDeepShortcuts.isEmpty()) { - ItemInfoMatcher matcher = ItemInfoMatcher.ofShortcutKeys(removedDeepShortcuts); - mWorkspace.removeItemsByMatcher(matcher); - mDragController.onAppsRemoved(matcher); - } - } } /** @@ -3836,28 +3805,17 @@ public class Launcher extends BaseActivity * package-removal should clear all items by package name. */ @Override - public void bindWorkspaceComponentsRemoved( - final HashSet<String> packageNames, final HashSet<ComponentName> components, - final UserHandle user) { + public void bindWorkspaceComponentsRemoved(final ItemInfoMatcher matcher) { Runnable r = new Runnable() { public void run() { - bindWorkspaceComponentsRemoved(packageNames, components, user); + bindWorkspaceComponentsRemoved(matcher); } }; if (waitUntilResume(r)) { return; } - if (!packageNames.isEmpty()) { - ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageNames, user); - mWorkspace.removeItemsByMatcher(matcher); - mDragController.onAppsRemoved(matcher); - - } - if (!components.isEmpty()) { - ItemInfoMatcher matcher = ItemInfoMatcher.ofComponents(components, user); - mWorkspace.removeItemsByMatcher(matcher); - mDragController.onAppsRemoved(matcher); - } + mWorkspace.removeItemsByMatcher(matcher); + mDragController.onAppsRemoved(matcher); } @Override diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 22d62ec5c..a906b00f1 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -56,6 +56,7 @@ import com.android.launcher3.provider.LauncherDbUtils; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; @@ -148,13 +149,10 @@ public class LauncherModel extends BroadcastReceiver ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated); public void bindPromiseAppProgressUpdated(PromiseAppInfo app); - public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, - ArrayList<ShortcutInfo> removed, UserHandle user); + public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, UserHandle user); public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); public void bindRestoreItemsChange(HashSet<ItemInfo> updates); - public void bindWorkspaceComponentsRemoved( - HashSet<String> packageNames, HashSet<ComponentName> components, - UserHandle user); + public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher); public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos); public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets); public void onPageBoundSynchronously(int page); diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java index 9ff61ec98..e2474900d 100644 --- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java +++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java @@ -80,13 +80,11 @@ import com.android.launcher3.widget.WidgetsContainerView; public class LauncherStateTransitionAnimation { /** - * animation used for all apps and widget tray when - *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code false} + * animation used for the widget tray */ public static final int CIRCULAR_REVEAL = 0; /** - * animation used for all apps and not widget tray when - *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code true} + * animation used for all apps tray */ public static final int PULLUP = 1; @@ -154,13 +152,9 @@ public class LauncherStateTransitionAnimation { mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); } }; - int animType = CIRCULAR_REVEAL; - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { - animType = PULLUP; - } // Only animate the search bar if animating from spring loaded mode back to all apps startAnimationToOverlay( - Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb); + Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, PULLUP, cb); } /** @@ -193,12 +187,8 @@ public class LauncherStateTransitionAnimation { if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED || mAllAppsController.isTransitioning()) { - int animType = CIRCULAR_REVEAL; - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { - animType = PULLUP; - } startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, - animated, animType, onCompleteRunnable); + animated, PULLUP, onCompleteRunnable); } else if (fromState == Launcher.State.WIDGETS || fromState == Launcher.State.WIDGETS_SPRING_LOADED) { startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, @@ -235,8 +225,7 @@ public class LauncherStateTransitionAnimation { playCommonTransitionAnimations(toWorkspaceState, animated, initialized, animation, layerViews); if (!animated || !initialized) { - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && - toWorkspaceState == Workspace.State.NORMAL_HIDDEN) { + if (toWorkspaceState == Workspace.State.NORMAL_HIDDEN) { mAllAppsController.finishPullUp(); } toView.setTranslationX(0.0f); @@ -527,8 +516,7 @@ public class LauncherStateTransitionAnimation { playCommonTransitionAnimations(toWorkspaceState, animated, initialized, animation, layerViews); if (!animated || !initialized) { - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && - fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) { + if (fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) { mAllAppsController.finishPullDown(); } fromView.setVisibility(View.GONE); diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java index 90463725f..5bdc1f5bd 100644 --- a/src/com/android/launcher3/SettingsActivity.java +++ b/src/com/android/launcher3/SettingsActivity.java @@ -98,6 +98,8 @@ public class SettingsActivity extends Activity { getPreferenceScreen().removePreference( findPreference(SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY)); getPreferenceScreen().removePreference(iconBadgingPref); + } else if (!getResources().getBoolean(R.bool.notification_badging_enabled)) { + getPreferenceScreen().removePreference(iconBadgingPref); } else { // Listen to system notification badge settings while this UI is active. mIconBadgingObserver = new IconBadgingObserver( diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java index e15cf9f50..84d6a9b34 100644 --- a/src/com/android/launcher3/UninstallDropTarget.java +++ b/src/com/android/launcher3/UninstallDropTarget.java @@ -11,12 +11,17 @@ import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.util.AttributeSet; +import android.util.Log; import android.widget.Toast; import com.android.launcher3.compat.LauncherAppsCompat; +import java.net.URISyntaxException; + public class UninstallDropTarget extends ButtonDropTarget { + private static final String TAG = "UninstallDropTarget"; + public UninstallDropTarget(Context context, AttributeSet attrs) { this(context, attrs, 0); } @@ -99,25 +104,28 @@ public class UninstallDropTarget extends ButtonDropTarget { final Launcher launcher, ItemInfo info, DropTargetResultCallback callback) { final ComponentName cn = getUninstallTarget(launcher, info); - final boolean isUninstallable; + boolean canUninstall; if (cn == null) { // System applications cannot be installed. For now, show a toast explaining that. // We may give them the option of disabling apps this way. Toast.makeText(launcher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show(); - isUninstallable = false; + canUninstall = false; } else { - Intent intent = new Intent(Intent.ACTION_DELETE, - Uri.fromParts("package", cn.getPackageName(), cn.getClassName())) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - intent.putExtra(Intent.EXTRA_USER, info.user); - launcher.startActivity(intent); - isUninstallable = true; + try { + Intent i = Intent.parseUri(launcher.getString(R.string.delete_package_intent), 0) + .setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName())) + .putExtra(Intent.EXTRA_USER, info.user); + launcher.startActivity(i); + canUninstall = true; + } catch (URISyntaxException e) { + Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info); + canUninstall = false; + } } if (callback != null) { - sendUninstallResult(launcher, isUninstallable, cn, info.user, callback); + sendUninstallResult(launcher, canUninstall, cn, info.user, callback); } - return isUninstallable; + return canUninstall; } /** diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java index 76772dce4..a105a7303 100644 --- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java +++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java @@ -273,9 +273,8 @@ public class WorkspaceStateTransitionAnimation { float finalBackgroundAlpha = (states.stateIsSpringLoaded || states.stateIsOverview) ? 1.0f : 0f; float finalHotseatAlpha = (states.stateIsNormal || states.stateIsSpringLoaded || - (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && states.stateIsNormalHidden)) ? 1f : 0f; - float finalQsbAlpha = (states.stateIsNormal || - (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && states.stateIsNormalHidden)) ? 1f : 0f; + states.stateIsNormalHidden) ? 1f : 0f; + float finalQsbAlpha = (states.stateIsNormal || states.stateIsNormalHidden) ? 1f : 0f; float finalWorkspaceTranslationY = 0; if (states.stateIsOverview || states.stateIsOverviewHidden) { @@ -312,8 +311,7 @@ public class WorkspaceStateTransitionAnimation { if (states.stateIsOverviewHidden) { finalAlpha = 0f; } else if(states.stateIsNormalHidden) { - finalAlpha = (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && - i == mWorkspace.getNextPage()) ? 1 : 0; + finalAlpha = (i == mWorkspace.getNextPage()) ? 1 : 0; } else if (states.stateIsNormal && mWorkspaceFadeInAdjacentScreens) { finalAlpha = (i == toPage || i < customPageCount) ? 1f : 0f; } else { @@ -322,7 +320,7 @@ public class WorkspaceStateTransitionAnimation { // If we are animating to/from the small state, then hide the side pages and fade the // current page in - if (!FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && !mWorkspace.isSwitchingState()) { + if (!FeatureFlags.NO_ALL_APPS_ICON && !mWorkspace.isSwitchingState()) { if (states.workspaceToAllApps || states.allAppsToWorkspace) { boolean isCurrentPage = (i == toPage); if (states.allAppsToWorkspace && isCurrentPage) { diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 0d512ab42..97a87c16c 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -102,18 +102,14 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc @Override protected void updateBackground( int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) { - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { - if (mLauncher.getDeviceProfile().isVerticalBarLayout()) { - getRevealView().setBackground(new InsetDrawable(mBaseDrawable, - paddingLeft, paddingTop, paddingRight, paddingBottom)); - getContentView().setBackground( - new InsetDrawable(new ColorDrawable(Color.TRANSPARENT), - paddingLeft, paddingTop, paddingRight, paddingBottom)); - } else { - getRevealView().setBackground(mBaseDrawable); - } + if (mLauncher.getDeviceProfile().isVerticalBarLayout()) { + getRevealView().setBackground(new InsetDrawable(mBaseDrawable, + paddingLeft, paddingTop, paddingRight, paddingBottom)); + getContentView().setBackground( + new InsetDrawable(new ColorDrawable(Color.TRANSPARENT), + paddingLeft, paddingTop, paddingRight, paddingBottom)); } else { - super.updateBackground(paddingLeft, paddingTop, paddingRight, paddingBottom); + getRevealView().setBackground(mBaseDrawable); } } @@ -232,11 +228,9 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mAppsRecyclerView.preMeasureViews(mAdapter); mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener()); - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { - getRevealView().setVisibility(View.VISIBLE); - getContentView().setVisibility(View.VISIBLE); - getContentView().setBackground(null); - } + getRevealView().setVisibility(View.VISIBLE); + getContentView().setVisibility(View.VISIBLE); + getContentView().setBackground(null); } public SearchUiManager getSearchUiManager() { @@ -254,32 +248,15 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc // Update the number of items in the grid before we measure the view grid.updateAppsViewNumCols(); - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { - if (mNumAppsPerRow != grid.inv.numColumns || - mNumPredictedAppsPerRow != grid.inv.numColumns) { - mNumAppsPerRow = grid.inv.numColumns; - mNumPredictedAppsPerRow = grid.inv.numColumns; - - mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow); - mAdapter.setNumAppsPerRow(mNumAppsPerRow); - mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow); - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - return; - } - - // --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. --- - if (mNumAppsPerRow != grid.allAppsNumCols || - mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) { - mNumAppsPerRow = grid.allAppsNumCols; - mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols; + if (mNumAppsPerRow != grid.inv.numColumns || + mNumPredictedAppsPerRow != grid.inv.numColumns) { + mNumAppsPerRow = grid.inv.numColumns; + mNumPredictedAppsPerRow = grid.inv.numColumns; mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow); mAdapter.setNumAppsPerRow(mNumAppsPerRow); mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow); } - - // --- remove END when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. --- super.onMeasure(widthMeasureSpec, heightMeasureSpec); } diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java index 39e208874..d50455171 100644 --- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java +++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java @@ -108,8 +108,7 @@ public class AppsSearchContainerLayout extends FrameLayout @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && - !mLauncher.getDeviceProfile().isVerticalBarLayout()) { + if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) { getLayoutParams().height = mLauncher.getDragLayer().getInsets().top + mMinHeight; } super.onMeasure(widthMeasureSpec, heightMeasureSpec); diff --git a/src/com/android/launcher3/compat/WallpaperManagerCompatVOMR1.java b/src/com/android/launcher3/compat/WallpaperManagerCompatVOMR1.java index 6233fabb2..fe5ff2a8c 100644 --- a/src/com/android/launcher3/compat/WallpaperManagerCompatVOMR1.java +++ b/src/com/android/launcher3/compat/WallpaperManagerCompatVOMR1.java @@ -20,6 +20,7 @@ import android.app.WallpaperManager; import android.content.Context; import android.graphics.Color; import android.os.Build; +import android.os.Handler; import android.support.annotation.Nullable; import android.util.Log; @@ -48,8 +49,7 @@ public class WallpaperManagerCompatVOMR1 extends WallpaperManagerCompat { mOCLClass = Class.forName("android.app.WallpaperManager$OnColorsChangedListener"); mAddOCLMethod = WallpaperManager.class.getDeclaredMethod( - "addOnColorsChangedListener", mOCLClass); - + "addOnColorsChangedListener", mOCLClass, Handler.class); mWCGetMethod = WallpaperManager.class.getDeclaredMethod("getWallpaperColors", int.class); Class wallpaperColorsClass = mWCGetMethod.getReturnType(); mWCGetPrimaryColorMethod = wallpaperColorsClass.getDeclaredMethod("getPrimaryColor"); @@ -89,7 +89,7 @@ public class WallpaperManagerCompatVOMR1 extends WallpaperManagerCompat { } }); try { - mAddOCLMethod.invoke(mWm, onChangeListener); + mAddOCLMethod.invoke(mWm, onChangeListener, null); } catch (Exception e) { Log.e(TAG, "Error calling wallpaper API", e); } diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index 7964dd15d..6a4cbcbf0 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -34,7 +34,6 @@ abstract class BaseFlags { public static boolean LAUNCHER3_DISABLE_ICON_NORMALIZATION = false; public static boolean LAUNCHER3_LEGACY_FOLDER_ICON = false; public static boolean LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW = false; - public static boolean LAUNCHER3_ALL_APPS_PULL_UP = true; public static boolean LAUNCHER3_NEW_FOLDER_ANIMATION = true; // When enabled allows to use any point on the fast scrollbar to start dragging. public static final boolean LAUNCHER3_DIRECT_SCROLL = true; @@ -57,8 +56,6 @@ abstract class BaseFlags { public static final boolean PULLDOWN_SEARCH = false; // When enabled the status bar may show dark icons based on the top of the wallpaper. public static final boolean LIGHT_STATUS_BAR = false; - // When enabled icons are badged with the number of notifications associated with that app. - public static final boolean BADGE_ICONS = true; // When enabled, icons not supporting {@link AdaptiveIconDrawable} will be wrapped in {@link FixedScaleDrawable}. public static final boolean LEGACY_ICON_TREATMENT = true; // When enabled, adaptive icons would have shadows baked when being stored to icon cache. diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index be5f01adb..fde7995ce 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -243,7 +243,7 @@ public class DragLayer extends InsettableFrameLayout { return true; } - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onControllerInterceptTouchEvent(ev)) { + if (mAllAppsController.onControllerInterceptTouchEvent(ev)) { mActiveController = mAllAppsController; return true; } @@ -544,7 +544,6 @@ public class DragLayer extends InsettableFrameLayout { public void clearResizeFrame() { if (mCurrentResizeFrame != null) { - mCurrentResizeFrame.commitResize(); removeView(mCurrentResizeFrame); mCurrentResizeFrame = null; } diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java index 9b4510fca..d5b5aa7cf 100644 --- a/src/com/android/launcher3/model/BaseModelUpdateTask.java +++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java @@ -26,6 +26,7 @@ import com.android.launcher3.LauncherModel.CallbackTask; import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.MultiHashMap; import java.util.ArrayList; @@ -94,19 +95,12 @@ public abstract class BaseModelUpdateTask implements ModelUpdateTask { public void bindUpdatedShortcuts( - ArrayList<ShortcutInfo> updatedShortcuts, UserHandle user) { - bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user); - } - - public void bindUpdatedShortcuts( - final ArrayList<ShortcutInfo> updatedShortcuts, - final ArrayList<ShortcutInfo> removedShortcuts, - final UserHandle user) { - if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) { + final ArrayList<ShortcutInfo> updatedShortcuts, final UserHandle user) { + if (!updatedShortcuts.isEmpty()) { scheduleCallbackTask(new CallbackTask() { @Override public void execute(Callbacks callbacks) { - callbacks.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user); + callbacks.bindShortcutsChanged(updatedShortcuts, user); } }); } @@ -132,4 +126,16 @@ public abstract class BaseModelUpdateTask implements ModelUpdateTask { } }); } + + public void deleteAndBindComponentsRemoved(final ItemInfoMatcher matcher) { + getModelWriter().deleteItemsFromDatabase(matcher); + + // Call the components-removed callback + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindWorkspaceComponentsRemoved(matcher); + } + }); + } } diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index c6e878cf1..6c78d5bfc 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -43,6 +43,7 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.ItemInfoMatcher; +import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; @@ -172,7 +173,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { // Update shortcut infos if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) { final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>(); - final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<>(); + final LongArrayMap<Boolean> removedShortcuts = new LongArrayMap<>(); final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>(); synchronized (dataModel) { @@ -213,7 +214,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { } if ((intent == null) || (appInfo == null)) { - removedShortcuts.add(si); + removedShortcuts.put(si.id, true); continue; } si.intent = intent; @@ -267,9 +268,9 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { } } - bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser); + bindUpdatedShortcuts(updatedShortcuts, mUser); if (!removedShortcuts.isEmpty()) { - getModelWriter().deleteItemsFromDatabase(removedShortcuts); + deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedShortcuts, false)); } if (!widgets.isEmpty()) { @@ -306,22 +307,12 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { } if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) { - getModelWriter().deleteItemsFromDatabase( - ItemInfoMatcher.ofPackages(removedPackages, mUser)); - getModelWriter().deleteItemsFromDatabase( - ItemInfoMatcher.ofComponents(removedComponents, mUser)); + ItemInfoMatcher removeMatch = ItemInfoMatcher.ofPackages(removedPackages, mUser) + .or(ItemInfoMatcher.ofComponents(removedComponents, mUser)); + deleteAndBindComponentsRemoved(removeMatch); // Remove any queued items from the install queue InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser); - - // Call the components-removed callback - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindWorkspaceComponentsRemoved( - removedPackages, removedComponents, mUser); - } - }); } if (!removedApps.isEmpty()) { diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java index 17cc238d4..c1f33a6b2 100644 --- a/src/com/android/launcher3/model/ShortcutsChangedTask.java +++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java @@ -26,9 +26,12 @@ import com.android.launcher3.ShortcutInfo; import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; +import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.MultiHashMap; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; /** @@ -56,33 +59,35 @@ public class ShortcutsChangedTask extends BaseModelUpdateTask { deepShortcutManager.onShortcutsChanged(mShortcuts); // Find ShortcutInfo's that have changed on the workspace. - final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>(); - MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>(); + HashSet<ShortcutKey> removedKeys = new HashSet<>(); + MultiHashMap<ShortcutKey, ShortcutInfo> keyToShortcutInfo = new MultiHashMap<>(); + HashSet<String> allIds = new HashSet<>(); + for (ItemInfo itemInfo : dataModel.itemsIdMap) { if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { ShortcutInfo si = (ShortcutInfo) itemInfo; - if (si.getIntent().getPackage().equals(mPackageName) - && si.user.equals(mUser)) { - idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si); + if (si.getIntent().getPackage().equals(mPackageName) && si.user.equals(mUser)) { + keyToShortcutInfo.addToList(ShortcutKey.fromItemInfo(si), si); + allIds.add(si.getDeepShortcutId()); } } } final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>(); - if (!idsToWorkspaceShortcutInfos.isEmpty()) { + if (!keyToShortcutInfo.isEmpty()) { // Update the workspace to reflect the changes to updated shortcuts residing on it. List<ShortcutInfoCompat> shortcuts = deepShortcutManager.queryForFullDetails( - mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser); + mPackageName, new ArrayList<>(allIds), mUser); for (ShortcutInfoCompat fullDetails : shortcuts) { - List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos - .remove(fullDetails.getId()); + ShortcutKey key = ShortcutKey.fromInfo(fullDetails); + List<ShortcutInfo> shortcutInfos = keyToShortcutInfo.remove(key); if (!fullDetails.isPinned()) { // The shortcut was previously pinned but is no longer, so remove it from // the workspace and our pinned shortcut counts. // Note that we put this check here, after querying for full details, // because there's a possible race condition between pinning and // receiving this callback. - removedShortcutInfos.addAll(shortcutInfos); + removedKeys.add(key); continue; } for (final ShortcutInfo shortcutInfo : shortcutInfos) { @@ -94,16 +99,14 @@ public class ShortcutsChangedTask extends BaseModelUpdateTask { } } - // If there are still entries in idsToWorkspaceShortcutInfos, that means that + // If there are still entries in keyToShortcutInfo, that means that // the corresponding shortcuts weren't passed in onShortcutsChanged(). This // means they were cleared, so we remove and unpin them now. - for (String id : idsToWorkspaceShortcutInfos.keySet()) { - removedShortcutInfos.addAll(idsToWorkspaceShortcutInfos.get(id)); - } + removedKeys.addAll(keyToShortcutInfo.keySet()); - bindUpdatedShortcuts(updatedShortcutInfos, removedShortcutInfos, mUser); - if (!removedShortcutInfos.isEmpty()) { - getModelWriter().deleteItemsFromDatabase(removedShortcutInfos); + bindUpdatedShortcuts(updatedShortcutInfos, mUser); + if (!keyToShortcutInfo.isEmpty()) { + deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(removedKeys)); } if (mUpdateIdMap) { diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java index 802771f04..8170f9a67 100644 --- a/src/com/android/launcher3/model/UserLockStateChangedTask.java +++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java @@ -29,9 +29,11 @@ import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.ItemInfoMatcher; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -70,17 +72,19 @@ public class UserLockStateChangedTask extends BaseModelUpdateTask { // Update the workspace to reflect the changes to updated shortcuts residing on it. ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>(); - ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>(); + HashSet<ShortcutKey> removedKeys = new HashSet<>(); + for (ItemInfo itemInfo : dataModel.itemsIdMap) { if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT && mUser.equals(itemInfo.user)) { ShortcutInfo si = (ShortcutInfo) itemInfo; if (isUserUnlocked) { - ShortcutInfoCompat shortcut = pinnedShortcuts.get(ShortcutKey.fromItemInfo(si)); + ShortcutKey key = ShortcutKey.fromItemInfo(si); + ShortcutInfoCompat shortcut = pinnedShortcuts.get(key); // We couldn't verify the shortcut during loader. If its no longer available // (probably due to clear data), delete the workspace item as well if (shortcut == null) { - deletedShortcutInfos.add(si); + removedKeys.add(key); continue; } si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER; @@ -93,9 +97,9 @@ public class UserLockStateChangedTask extends BaseModelUpdateTask { updatedShortcutInfos.add(si); } } - bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser); - if (!deletedShortcutInfos.isEmpty()) { - getModelWriter().deleteItemsFromDatabase(deletedShortcutInfos); + bindUpdatedShortcuts(updatedShortcutInfos, mUser); + if (!removedKeys.isEmpty()) { + deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(removedKeys)); } // Remove shortcut id map for that user diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java index 6a7098915..91266263f 100644 --- a/src/com/android/launcher3/notification/NotificationListener.java +++ b/src/com/android/launcher3/notification/NotificationListener.java @@ -32,7 +32,6 @@ import android.util.Log; import android.util.Pair; import com.android.launcher3.LauncherModel; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.SettingsObserver; @@ -164,9 +163,6 @@ public class NotificationListener extends NotificationListenerService { } public static void setNotificationsChangedListener(NotificationsChangedListener listener) { - if (!FeatureFlags.BADGE_ICONS) { - return; - } sNotificationsChangedListener = listener; NotificationListener notificationListener = getInstanceIfConnected(); diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java index 42de28466..18787b6a2 100644 --- a/src/com/android/launcher3/util/ItemInfoMatcher.java +++ b/src/com/android/launcher3/util/ItemInfoMatcher.java @@ -18,6 +18,7 @@ package com.android.launcher3.util; import android.content.ComponentName; import android.os.UserHandle; +import android.util.SparseLongArray; import com.android.launcher3.FolderInfo; import com.android.launcher3.ItemInfo; @@ -66,6 +67,32 @@ public abstract class ItemInfoMatcher { return filtered; } + /** + * Returns a new matcher with returns true if either this or {@param matcher} returns true. + */ + public ItemInfoMatcher or(final ItemInfoMatcher matcher) { + final ItemInfoMatcher that = this; + return new ItemInfoMatcher() { + @Override + public boolean matches(ItemInfo info, ComponentName cn) { + return that.matches(info, cn) || matcher.matches(info, cn); + } + }; + } + + /** + * Returns a new matcher with returns true if both this and {@param matcher} returns true. + */ + public ItemInfoMatcher and(final ItemInfoMatcher matcher) { + final ItemInfoMatcher that = this; + return new ItemInfoMatcher() { + @Override + public boolean matches(ItemInfo info, ComponentName cn) { + return that.matches(info, cn) && matcher.matches(info, cn); + } + }; + } + public static ItemInfoMatcher ofUser(final UserHandle user) { return new ItemInfoMatcher() { @Override @@ -104,4 +131,14 @@ public abstract class ItemInfoMatcher { } }; } + + public static ItemInfoMatcher ofItemIds( + final LongArrayMap<Boolean> ids, final Boolean matchDefault) { + return new ItemInfoMatcher() { + @Override + public boolean matches(ItemInfo info, ComponentName cn) { + return ids.get(info.id, matchDefault); + } + }; + } } diff --git a/src/com/android/launcher3/widget/WidgetListRowEntry.java b/src/com/android/launcher3/widget/WidgetListRowEntry.java index 3e89eeb9b..335b8c759 100644 --- a/src/com/android/launcher3/widget/WidgetListRowEntry.java +++ b/src/com/android/launcher3/widget/WidgetListRowEntry.java @@ -41,4 +41,8 @@ public class WidgetListRowEntry { this.widgets = items; } + @Override + public String toString() { + return pkgItem.packageName + ":" + widgets.size(); + } } diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 14a9d17ed..acec3dd3b 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -17,10 +17,12 @@ package com.android.launcher3.widget; import android.content.Context; +import android.content.pm.LauncherApps; import android.graphics.Point; import android.support.v7.widget.LinearLayoutManager; import android.util.AttributeSet; import android.util.Log; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; @@ -31,8 +33,10 @@ import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.folder.Folder; import com.android.launcher3.model.PackageItemInfo; @@ -74,7 +78,11 @@ public class WidgetsContainerView extends BaseContainerView public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mLauncher = Launcher.getLauncher(context); - mAdapter = new WidgetsListAdapter(this, this, context); + LauncherAppState apps = LauncherAppState.getInstance(context); + mAdapter = new WidgetsListAdapter(context, LayoutInflater.from(context), + apps.getWidgetCache(), new AlphabeticIndexCompat(context), this, this, + new WidgetsDiffReporter(apps.getIconCache())); + mAdapter.setNotifyListener(); if (LOGD) { Log.d(TAG, "WidgetsContainerView constructor"); } @@ -232,7 +240,6 @@ public class WidgetsContainerView extends BaseContainerView */ public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> model) { mAdapter.setWidgets(model); - mAdapter.notifyDataSetChanged(); View loader = getContentView().findViewById(R.id.loader); if (loader != null) { diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java new file mode 100644 index 000000000..d9c9ef9e3 --- /dev/null +++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java @@ -0,0 +1,140 @@ +/* + * 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.widget; + +import android.util.Log; + +import com.android.launcher3.IconCache; +import com.android.launcher3.model.PackageItemInfo; +import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * Do diff on widget's tray list items and call the {@link NotifyListener} methods accordingly. + */ +public class WidgetsDiffReporter { + private final boolean DEBUG = true; + private final String TAG = "WidgetsDiffReporter"; + private final IconCache mIconCache; + private NotifyListener mListener; + + public interface NotifyListener { + void notifyDataSetChanged(); + void notifyItemChanged(int index); + void notifyItemInserted(int index); + void notifyItemRemoved(int index); + } + + public WidgetsDiffReporter(IconCache iconCache) { + mIconCache = iconCache; + } + + public void setListener(NotifyListener listener) { + mListener = listener; + } + + public void process(ArrayList<WidgetListRowEntry> currentEntries, + ArrayList<WidgetListRowEntry> newEntries, WidgetListRowEntryComparator comparator) { + if (DEBUG) { + Log.d(TAG, "process oldEntries#=" + currentEntries.size() + + " newEntries#=" + newEntries.size()); + } + if (currentEntries.size() == 0 && newEntries.size() > 0) { + currentEntries.addAll(newEntries); + mListener.notifyDataSetChanged(); + return; + } + ArrayList<WidgetListRowEntry> orgEntries = + (ArrayList<WidgetListRowEntry>) currentEntries.clone(); + Iterator<WidgetListRowEntry> orgIter = orgEntries.iterator(); + Iterator<WidgetListRowEntry> newIter = newEntries.iterator(); + + WidgetListRowEntry orgRowEntry = orgIter.next(); + WidgetListRowEntry newRowEntry = newIter.next(); + + do { + int diff = comparePackageName(orgRowEntry, newRowEntry, comparator); + if (DEBUG) { + Log.d(TAG, String.format("diff=%d orgRowEntry (%s) newRowEntry (%s)", + diff, orgRowEntry != null? orgRowEntry.toString() : null, + newRowEntry != null? newRowEntry.toString() : null)); + } + int index = -1; + if (diff < 0) { + index = currentEntries.indexOf(orgRowEntry); + mListener.notifyItemRemoved(index); + if (DEBUG) { + Log.d(TAG, String.format("notifyItemRemoved called (%d)%s", index, + orgRowEntry.titleSectionName)); + } + currentEntries.remove(index); + orgRowEntry = orgIter.hasNext() ? orgIter.next() : null; + } else if (diff > 0) { + index = orgRowEntry != null? currentEntries.indexOf(orgRowEntry): + currentEntries.size(); + currentEntries.add(index, newRowEntry); + newRowEntry = newIter.hasNext() ? newIter.next() : null; + mListener.notifyItemInserted(index); + if (DEBUG) { + Log.d(TAG, String.format("notifyItemInserted called (%d)%s", index, + newRowEntry.titleSectionName)); + } + } else { + // same package name but, + // did the icon, title, etc, change? + // or did the widget size and desc, span, etc change? + if (!isSamePackageItemInfo(orgRowEntry.pkgItem, newRowEntry.pkgItem) || + !orgRowEntry.widgets.equals(newRowEntry.widgets)) { + index = currentEntries.indexOf(orgRowEntry); + currentEntries.set(index, newRowEntry); + mListener.notifyItemChanged(index); + if (DEBUG) { + Log.d(TAG, String.format("notifyItemChanged called (%d)%s", index, + newRowEntry.titleSectionName)); + } + } + orgRowEntry = orgIter.hasNext() ? orgIter.next() : null; + newRowEntry = newIter.hasNext() ? newIter.next() : null; + } + } while(orgRowEntry != null || newRowEntry != null); + } + + /** + * Compare package name using the same comparator as in {@link WidgetsListAdapter}. + * Also handle null row pointers. + */ + private int comparePackageName(WidgetListRowEntry curRow, WidgetListRowEntry newRow, + WidgetListRowEntryComparator comparator) { + if (curRow == null && newRow == null) { + throw new IllegalStateException("Cannot compare PackageItemInfo if both rows are null."); + } + + if (curRow == null && newRow != null) { + return 1; // new row needs to be inserted + } else if (curRow != null && newRow == null) { + return -1; // old row needs to be deleted + } + return comparator.compare(curRow, newRow); + } + + private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) { + return curInfo.iconBitmap.equals(newInfo.iconBitmap) && + !mIconCache.isDefaultIcon(curInfo.iconBitmap, curInfo.user); + } +} diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java index a1eb0ab12..6b1800c67 100644 --- a/src/com/android/launcher3/widget/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java @@ -21,9 +21,10 @@ import android.support.v7.widget.RecyclerView.Adapter; import android.util.Log; import android.view.LayoutInflater; import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; import android.view.ViewGroup; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.compat.AlphabeticIndexCompat; @@ -55,40 +56,67 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { private final WidgetPreviewLoader mWidgetPreviewLoader; private final LayoutInflater mLayoutInflater; - - private final View.OnClickListener mIconClickListener; - private final View.OnLongClickListener mIconLongClickListener; - - private final ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>(); private final AlphabeticIndexCompat mIndexer; + private final OnClickListener mIconClickListener; + private final OnLongClickListener mIconLongClickListener; private final int mIndent; - - public WidgetsListAdapter(View.OnClickListener iconClickListener, - View.OnLongClickListener iconLongClickListener, - Context context) { - mLayoutInflater = LayoutInflater.from(context); - mWidgetPreviewLoader = LauncherAppState.getInstance(context).getWidgetCache(); - - mIndexer = new AlphabeticIndexCompat(context); - + private ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>(); + private final WidgetsDiffReporter mDiffReporter; + + public WidgetsListAdapter(Context context, LayoutInflater layoutInflater, + WidgetPreviewLoader widgetPreviewLoader, AlphabeticIndexCompat indexCompat, + OnClickListener iconClickListener, OnLongClickListener iconLongClickListener, + WidgetsDiffReporter diffReporter) { + mLayoutInflater = layoutInflater; + mWidgetPreviewLoader = widgetPreviewLoader; + mIndexer = indexCompat; mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent); + mDiffReporter = diffReporter; } + public void setNotifyListener() { + mDiffReporter.setListener(new WidgetsDiffReporter.NotifyListener() { + @Override + public void notifyDataSetChanged() { + WidgetsListAdapter.this.notifyDataSetChanged(); + } + + @Override + public void notifyItemChanged(int index) { + WidgetsListAdapter.this.notifyItemChanged(index); + } + + @Override + public void notifyItemInserted(int index) { + WidgetsListAdapter.this.notifyItemInserted(index); + } + + @Override + public void notifyItemRemoved(int index) { + WidgetsListAdapter.this.notifyItemRemoved(index); + } + }); + } + + /** + * Update the widget list. + */ public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets) { - mEntries.clear(); - WidgetItemComparator widgetComparator = new WidgetItemComparator(); + ArrayList<WidgetListRowEntry> tempEntries = new ArrayList<>(); + WidgetItemComparator widgetComparator = new WidgetItemComparator(); for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : widgets.entrySet()) { WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue()); row.titleSectionName = mIndexer.computeSectionName(row.pkgItem.title); Collections.sort(row.widgets, widgetComparator); - mEntries.add(row); + tempEntries.add(row); } - - Collections.sort(mEntries, new WidgetListRowEntryComparator()); + WidgetListRowEntryComparator rowComparator = new WidgetListRowEntryComparator(); + Collections.sort(tempEntries, rowComparator); + mDiffReporter.process(mEntries, tempEntries, rowComparator); } @Override diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 7fb5d8521..1be33d2f8 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -103,7 +103,7 @@ public abstract class AbstractLauncherUiTest { */ protected UiObject2 openAllApps() { mDevice.waitForIdle(); - if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { + if (FeatureFlags.NO_ALL_APPS_ICON) { // clicking on the page indicator brings up all apps tray on non tablets. findViewById(R.id.page_indicator).click(); } else { diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java new file mode 100644 index 000000000..40b65e4fb --- /dev/null +++ b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java @@ -0,0 +1,149 @@ +/* + * 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.widget; + +import android.appwidget.AppWidgetProviderInfo; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.LayoutInflater; + +import com.android.launcher3.IconCache; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.WidgetPreviewLoader; +import com.android.launcher3.compat.AlphabeticIndexCompat; +import com.android.launcher3.compat.AppWidgetManagerCompat; +import com.android.launcher3.model.PackageItemInfo; +import com.android.launcher3.model.WidgetItem; +import com.android.launcher3.util.MultiHashMap; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class WidgetsListAdapterTest { + + private final String TAG = "WidgetsListAdapterTest"; + + @Mock private LayoutInflater mMockLayoutInflater; + @Mock private WidgetPreviewLoader mMockWidgetCache; + @Mock private WidgetsDiffReporter.NotifyListener mListener; + @Mock private IconCache mIconCache; + + private WidgetsListAdapter mAdapter; + private AlphabeticIndexCompat mIndexCompat; + private InvariantDeviceProfile mTestProfile; + private Context mContext; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = InstrumentationRegistry.getTargetContext(); + mTestProfile = new InvariantDeviceProfile(); + mTestProfile.numRows = 5; + mTestProfile.numColumns = 5; + mIndexCompat = new AlphabeticIndexCompat(mContext); + WidgetsDiffReporter reporter = new WidgetsDiffReporter(mIconCache); + reporter.setListener(mListener); + mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache, + mIndexCompat, null, null, reporter); + } + + @Test + public void test_notifyDataSetChanged() throws Exception { + mAdapter.setWidgets(generateSampleMap(1)); + verify(mListener, times(1)).notifyDataSetChanged(); + } + + @Test + public void test_notifyItemInserted() throws Exception { + mAdapter.setWidgets(generateSampleMap(1)); + mAdapter.setWidgets(generateSampleMap(2)); + verify(mListener, times(1)).notifyDataSetChanged(); + verify(mListener, times(1)).notifyItemInserted(1); + } + + @Test + public void test_notifyItemRemoved() throws Exception { + mAdapter.setWidgets(generateSampleMap(2)); + mAdapter.setWidgets(generateSampleMap(1)); + verify(mListener, times(1)).notifyDataSetChanged(); + verify(mListener, times(1)).notifyItemRemoved(1); + } + + @Test + public void testNotifyItemChanged_PackageIconDiff() throws Exception { + mAdapter.setWidgets(generateSampleMap(1)); + mAdapter.setWidgets(generateSampleMap(1)); + verify(mListener, times(1)).notifyDataSetChanged(); + verify(mListener, times(1)).notifyItemChanged(0); + } + + @Test + public void testNotifyItemChanged_widgetItemInfoDiff() throws Exception { + // TODO: same package name but item number changed + } + + @Test + public void testNotifyItemInsertedRemoved_hodgepodge() throws Exception { + // TODO: insert and remove combined. curMap + // newMap [A, C, D] [A, B, E] + // B - C < 0, removed B from index 1 [A, E] + // E - C > 0, C inserted to index 1 [A, C, E] + // E - D > 0, D inserted to index 2 [A, C, D, E] + // E - null = -1, E deleted from index 3 [A, C, D] + } + + /** + * Helper method to generate the sample widget model map that can be used for the tests + * @param num the number of WidgetItem the map should contain + * @return + */ + private MultiHashMap<PackageItemInfo, WidgetItem> generateSampleMap(int num) { + MultiHashMap<PackageItemInfo, WidgetItem> newMap = new MultiHashMap(); + if (num <= 0) return newMap; + + PackageManager pm = mContext.getPackageManager(); + AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(mContext); + for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(null)) { + WidgetItem wi = new WidgetItem(LauncherAppWidgetProviderInfo + .fromProviderInfo(mContext, widgetInfo), pm, mTestProfile); + + PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName()); + pInfo.title = pInfo.packageName; + pInfo.user = wi.user; + pInfo.iconBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8); + newMap.addToList(pInfo, wi); + if (newMap.size() == num) { + break; + } + } + return newMap; + } +} |