summaryrefslogtreecommitdiffstats
path: root/src/com/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android')
-rw-r--r--src/com/android/launcher3/AbstractFloatingView.java2
-rw-r--r--src/com/android/launcher3/AllAppsList.java10
-rw-r--r--src/com/android/launcher3/AppInfo.java6
-rw-r--r--src/com/android/launcher3/AppWidgetsRestoredReceiver.java2
-rw-r--r--src/com/android/launcher3/BaseActivity.java10
-rw-r--r--src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java4
-rw-r--r--src/com/android/launcher3/BubbleTextView.java104
-rw-r--r--src/com/android/launcher3/ButtonDropTarget.java5
-rw-r--r--src/com/android/launcher3/CellLayout.java13
-rw-r--r--src/com/android/launcher3/DeferredHandler.java120
-rw-r--r--src/com/android/launcher3/DeviceProfile.java12
-rw-r--r--src/com/android/launcher3/ExtendedEditText.java14
-rw-r--r--src/com/android/launcher3/FastBitmapDrawable.java182
-rw-r--r--src/com/android/launcher3/FocusHelper.java4
-rw-r--r--src/com/android/launcher3/FolderInfo.java7
-rw-r--r--src/com/android/launcher3/InstallShortcutReceiver.java4
-rw-r--r--src/com/android/launcher3/InvariantDeviceProfile.java3
-rw-r--r--src/com/android/launcher3/ItemInfoWithIcon.java4
-rw-r--r--src/com/android/launcher3/Launcher.java81
-rw-r--r--src/com/android/launcher3/LauncherAnimUtils.java13
-rw-r--r--src/com/android/launcher3/LauncherAppState.java13
-rw-r--r--src/com/android/launcher3/LauncherCallbacks.java1
-rw-r--r--src/com/android/launcher3/LauncherModel.java371
-rw-r--r--src/com/android/launcher3/LauncherProvider.java53
-rw-r--r--src/com/android/launcher3/LauncherSettings.java11
-rw-r--r--src/com/android/launcher3/LauncherStateTransitionAnimation.java2
-rw-r--r--src/com/android/launcher3/MainThreadExecutor.java4
-rw-r--r--src/com/android/launcher3/SessionCommitReceiver.java72
-rw-r--r--src/com/android/launcher3/SettingsActivity.java6
-rw-r--r--src/com/android/launcher3/ShortcutInfo.java3
-rw-r--r--src/com/android/launcher3/UninstallDropTarget.java8
-rw-r--r--src/com/android/launcher3/Utilities.java4
-rw-r--r--src/com/android/launcher3/Workspace.java16
-rw-r--r--src/com/android/launcher3/allapps/AllAppsContainerView.java20
-rw-r--r--src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java48
-rw-r--r--src/com/android/launcher3/allapps/AllAppsGridAdapter.java109
-rw-r--r--src/com/android/launcher3/allapps/AllAppsRecyclerView.java64
-rw-r--r--src/com/android/launcher3/allapps/AllAppsSearchBarController.java29
-rw-r--r--src/com/android/launcher3/allapps/AlphabeticalAppsList.java107
-rw-r--r--src/com/android/launcher3/anim/CircleRevealOutlineProvider.java (renamed from src/com/android/launcher3/util/CircleRevealOutlineProvider.java)2
-rw-r--r--src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java (renamed from src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java)24
-rw-r--r--src/com/android/launcher3/anim/PillRevealOutlineProvider.java (renamed from src/com/android/launcher3/util/PillRevealOutlineProvider.java)2
-rw-r--r--src/com/android/launcher3/anim/PropertyResetListener.java41
-rw-r--r--src/com/android/launcher3/anim/RevealOutlineAnimation.java (renamed from src/com/android/launcher3/util/RevealOutlineAnimation.java)6
-rw-r--r--src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java57
-rw-r--r--src/com/android/launcher3/badge/BadgeRenderer.java18
-rw-r--r--src/com/android/launcher3/compat/LauncherAppsCompat.java3
-rw-r--r--src/com/android/launcher3/compat/LauncherAppsCompatVL.java28
-rw-r--r--src/com/android/launcher3/compat/LauncherAppsCompatVO.java8
-rw-r--r--src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java88
-rw-r--r--src/com/android/launcher3/discovery/AppDiscoveryItem.java62
-rw-r--r--src/com/android/launcher3/discovery/AppDiscoveryItemView.java100
-rw-r--r--src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java21
-rw-r--r--src/com/android/launcher3/discovery/RatingView.java94
-rw-r--r--src/com/android/launcher3/dragndrop/DragView.java9
-rw-r--r--src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java25
-rw-r--r--src/com/android/launcher3/folder/Folder.java187
-rw-r--r--src/com/android/launcher3/folder/FolderAnimationManager.java357
-rw-r--r--src/com/android/launcher3/folder/FolderIcon.java242
-rw-r--r--src/com/android/launcher3/folder/FolderIconPreviewVerifier.java64
-rw-r--r--src/com/android/launcher3/folder/FolderPagedView.java68
-rw-r--r--src/com/android/launcher3/folder/StackFolderIconLayoutRule.java11
-rw-r--r--src/com/android/launcher3/graphics/DragPreviewProvider.java4
-rw-r--r--src/com/android/launcher3/graphics/DrawableFactory.java6
-rw-r--r--src/com/android/launcher3/graphics/HolographicOutlineHelper.java4
-rw-r--r--src/com/android/launcher3/graphics/IconPalette.java7
-rw-r--r--src/com/android/launcher3/graphics/LauncherIcons.java31
-rw-r--r--src/com/android/launcher3/graphics/PreloadIconDrawable.java3
-rw-r--r--src/com/android/launcher3/graphics/ShadowGenerator.java32
-rw-r--r--src/com/android/launcher3/logging/DumpTargetWrapper.java149
-rw-r--r--src/com/android/launcher3/logging/FileLog.java11
-rw-r--r--src/com/android/launcher3/logging/LoggerUtils.java33
-rw-r--r--src/com/android/launcher3/logging/UserEventDispatcher.java20
-rw-r--r--src/com/android/launcher3/model/BgDataModel.java110
-rw-r--r--src/com/android/launcher3/model/ModelWriter.java4
-rw-r--r--src/com/android/launcher3/model/SdCardAvailableReceiver.java2
-rw-r--r--src/com/android/launcher3/model/WidgetsModel.java7
-rw-r--r--src/com/android/launcher3/notification/NotificationFooterLayout.java168
-rw-r--r--src/com/android/launcher3/notification/NotificationHeaderView.java59
-rw-r--r--src/com/android/launcher3/notification/NotificationItemView.java85
-rw-r--r--src/com/android/launcher3/notification/NotificationListener.java40
-rw-r--r--src/com/android/launcher3/notification/NotificationMainView.java58
-rw-r--r--src/com/android/launcher3/popup/PopupContainerWithArrow.java250
-rw-r--r--src/com/android/launcher3/popup/PopupDataProvider.java6
-rw-r--r--src/com/android/launcher3/popup/PopupItemView.java71
-rw-r--r--src/com/android/launcher3/popup/PopupPopulator.java3
-rw-r--r--src/com/android/launcher3/provider/ImportDataTask.java10
-rw-r--r--src/com/android/launcher3/qsb/QsbContainerView.java12
-rw-r--r--src/com/android/launcher3/shortcuts/DeepShortcutManager.java3
-rw-r--r--src/com/android/launcher3/shortcuts/DeepShortcutView.java24
-rw-r--r--src/com/android/launcher3/shortcuts/ShortcutsItemView.java180
-rw-r--r--src/com/android/launcher3/testing/LauncherExtension.java3
-rw-r--r--src/com/android/launcher3/util/LooperExecutor.java (renamed from src/com/android/launcher3/util/LooperExecuter.java)4
-rw-r--r--src/com/android/launcher3/util/LooperIdleLock.java71
-rw-r--r--src/com/android/launcher3/util/ManagedProfileHeuristic.java15
-rw-r--r--src/com/android/launcher3/util/PackageManagerHelper.java48
-rw-r--r--src/com/android/launcher3/util/Preconditions.java10
-rw-r--r--src/com/android/launcher3/util/SQLiteCacheHelper.java4
-rw-r--r--src/com/android/launcher3/util/Themes.java19
-rw-r--r--src/com/android/launcher3/util/ViewOnDrawExecutor.java10
100 files changed, 3156 insertions, 1478 deletions
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 52a83dcdf..bd1268651 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -95,7 +95,7 @@ public abstract class AbstractFloatingView extends LinearLayout {
return null;
}
- protected static void closeOpenContainer(Launcher launcher, @FloatingViewType int type) {
+ public static void closeOpenContainer(Launcher launcher, @FloatingViewType int type) {
AbstractFloatingView view = getOpenView(launcher, type);
if (view != null) {
view.close(true);
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 9cce9b188..5b42cad96 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -208,16 +208,6 @@ public class AllAppsList {
}
/**
- * Query the launcher apps service for whether the supplied package has
- * MAIN/LAUNCHER activities in the supplied package.
- */
- static boolean packageHasActivities(Context context, String packageName,
- UserHandle user) {
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
- return launcherApps.getActivityList(packageName, user).size() > 0;
- }
-
- /**
* Returns whether <em>apps</em> contains <em>component</em>.
*/
private static boolean findActivity(ArrayList<AppInfo> apps, ComponentName component,
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 8bf49c27e..0ddde73c5 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -21,14 +21,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
import android.os.UserHandle;
-import android.util.Log;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
-import java.util.ArrayList;
-
/**
* Represents an app in AllAppsView.
*/
@@ -44,7 +41,7 @@ public class AppInfo extends ItemInfoWithIcon {
/**
* {@see ShortcutInfo#isDisabled}
*/
- int isDisabled = ShortcutInfo.DEFAULT;
+ public int isDisabled = ShortcutInfo.DEFAULT;
public AppInfo() {
itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
@@ -83,7 +80,6 @@ public class AppInfo extends ItemInfoWithIcon {
title = Utilities.trim(info.title);
intent = new Intent(info.intent);
isDisabled = info.isDisabled;
- iconBitmap = info.iconBitmap;
}
@Override
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index 7bc36921e..84a8bce6e 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -83,7 +83,7 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app != null) {
- app.reloadWorkspace();
+ app.getModel().forceReload();
}
asyncResult.finish();
}
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 9f4f1f9c4..e1a3ad0d6 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -21,9 +21,12 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.view.View.AccessibilityDelegate;
+import com.android.launcher3.logging.UserEventDispatcher;
+
public abstract class BaseActivity extends Activity {
protected DeviceProfile mDeviceProfile;
+ protected UserEventDispatcher mUserEventDispatcher;
public DeviceProfile getDeviceProfile() {
return mDeviceProfile;
@@ -33,6 +36,13 @@ public abstract class BaseActivity extends Activity {
return null;
}
+ public final UserEventDispatcher getUserEventDispatcher() {
+ if (mUserEventDispatcher == null) {
+ mUserEventDispatcher = UserEventDispatcher.get(this);
+ }
+ return mUserEventDispatcher;
+ }
+
public static BaseActivity fromContext(Context context) {
if (context instanceof BaseActivity) {
return (BaseActivity) context;
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index ba7c3f809..5feb42ea8 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -36,10 +36,6 @@ import com.android.launcher3.util.Themes;
*/
public class BaseRecyclerViewFastScrollBar {
- public interface FastScrollFocusableView {
- void setFastScrollFocusState(final FastBitmapDrawable.State focusState, boolean animated);
- }
-
private static final Property<BaseRecyclerViewFastScrollBar, Integer> TRACK_WIDTH =
new Property<BaseRecyclerViewFastScrollBar, Integer>(Integer.class, "width") {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 1a41e08db..f9a6742d8 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -40,11 +40,12 @@ import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.badge.BadgeInfo;
import com.android.launcher3.badge.BadgeRenderer;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.FolderIconPreviewVerifier;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.graphics.HolographicOutlineHelper;
-import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.popup.PopupContainerWithArrow;
import java.text.NumberFormat;
@@ -53,8 +54,7 @@ import java.text.NumberFormat;
* because we want to make the bubble taller than the text and TextView's clip is
* too aggressive.
*/
-public class BubbleTextView extends TextView
- implements BaseRecyclerViewFastScrollBar.FastScrollFocusableView, ItemInfoUpdateReceiver {
+public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver {
// Dimensions in DP
private static final float AMBIENT_SHADOW_RADIUS = 2.5f;
@@ -67,6 +67,8 @@ public class BubbleTextView extends TextView
private static final int DISPLAY_ALL_APPS = 1;
private static final int DISPLAY_FOLDER = 2;
+ private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
+
private final Launcher mLauncher;
private Drawable mIcon;
private final boolean mCenterVertically;
@@ -232,12 +234,19 @@ public class BubbleTextView extends TextView
}
@Override
- public void setPressed(boolean pressed) {
- super.setPressed(pressed);
-
+ public void refreshDrawableState() {
if (!mIgnorePressedStateChange) {
- updateIconState();
+ super.refreshDrawableState();
+ }
+ }
+
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ if (mStayPressed) {
+ mergeDrawableStates(drawableState, STATE_PRESSED);
}
+ return drawableState;
}
/** Returns the icon for this view. */
@@ -250,17 +259,6 @@ public class BubbleTextView extends TextView
return mLayoutHorizontal;
}
- private void updateIconState() {
- if (mIcon instanceof FastBitmapDrawable) {
- FastBitmapDrawable d = (FastBitmapDrawable) mIcon;
- if (isPressed() || mStayPressed) {
- d.animateState(FastBitmapDrawable.State.PRESSED);
- } else {
- d.animateState(FastBitmapDrawable.State.NORMAL);
- }
- }
- }
-
@Override
public void setOnLongClickListener(OnLongClickListener l) {
super.setOnLongClickListener(l);
@@ -334,7 +332,7 @@ public class BubbleTextView extends TextView
this, mPressedBackground);
}
- updateIconState();
+ refreshDrawableState();
}
void clearPressedBackground() {
@@ -364,7 +362,7 @@ public class BubbleTextView extends TextView
mPressedBackground = null;
mIgnorePressedStateChange = false;
- updateIconState();
+ refreshDrawableState();
return result;
}
@@ -505,15 +503,14 @@ public class BubbleTextView extends TextView
if (mIcon instanceof FastBitmapDrawable) {
BadgeInfo badgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo);
BadgeRenderer badgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer;
+ PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(mLauncher);
+ if (popup != null) {
+ popup.updateNotificationHeader(badgeInfo, itemInfo);
+ }
((FastBitmapDrawable) mIcon).applyIconBadge(badgeInfo, badgeRenderer, animate);
}
}
- public IconPalette getIconPalette() {
- return mIcon instanceof FastBitmapDrawable ? ((FastBitmapDrawable) mIcon).getIconPalette()
- : null;
- }
-
/**
* Sets the icon for this view based on the layout direction.
*/
@@ -544,10 +541,6 @@ public class BubbleTextView extends TextView
@Override
public void reapplyItemInfo(ItemInfoWithIcon info) {
if (getTag() == info) {
- FastBitmapDrawable.State prevState = FastBitmapDrawable.State.NORMAL;
- if (mIcon instanceof FastBitmapDrawable) {
- prevState = ((FastBitmapDrawable) mIcon).getCurrentState();
- }
mIconLoadRequest = null;
mDisableRelayout = true;
@@ -555,7 +548,9 @@ public class BubbleTextView extends TextView
applyFromApplicationInfo((AppInfo) info);
} else if (info instanceof ShortcutInfo) {
applyFromShortcutInfo((ShortcutInfo) info);
- if ((info.rank < FolderIcon.NUM_ITEMS_IN_PREVIEW) && (info.container >= 0)) {
+ FolderIconPreviewVerifier verifier =
+ new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
+ if (verifier.isItemInPreview(info.rank) && (info.container >= 0)) {
View folderIcon =
mLauncher.getWorkspace().getHomescreenIconByItemId(info.container);
if (folderIcon != null) {
@@ -566,12 +561,6 @@ public class BubbleTextView extends TextView
applyFromPackageItemInfo((PackageItemInfo) info);
}
- // If we are reapplying over an old icon, then we should update the new icon to the same
- // state as the old icon
- if (mIcon instanceof FastBitmapDrawable) {
- ((FastBitmapDrawable) mIcon).setState(prevState);
- }
-
mDisableRelayout = false;
}
}
@@ -593,34 +582,6 @@ public class BubbleTextView extends TextView
}
}
- @Override
- public void setFastScrollFocusState(final FastBitmapDrawable.State focusState, boolean animated) {
- // We can only set the fast scroll focus state on a FastBitmapDrawable
- if (!(mIcon instanceof FastBitmapDrawable)) {
- return;
- }
-
- FastBitmapDrawable d = (FastBitmapDrawable) mIcon;
- if (animated) {
- FastBitmapDrawable.State prevState = d.getCurrentState();
- if (d.animateState(focusState)) {
- // If the state was updated, then update the view accordingly
- animate().scaleX(focusState.viewScale)
- .scaleY(focusState.viewScale)
- .setStartDelay(getStartDelayForStateChange(prevState, focusState))
- .setDuration(d.getDurationForStateChange(prevState, focusState))
- .start();
- }
- } else {
- if (d.setState(focusState)) {
- // If the state was updated, then update the view accordingly
- animate().cancel();
- setScaleX(focusState.viewScale);
- setScaleY(focusState.viewScale);
- }
- }
- }
-
/**
* Returns true if the view can show custom shortcuts.
*/
@@ -629,19 +590,8 @@ public class BubbleTextView extends TextView
.isEmpty();
}
- /**
- * Returns the start delay when animating between certain {@link FastBitmapDrawable} states.
- */
- private static int getStartDelayForStateChange(final FastBitmapDrawable.State fromState,
- final FastBitmapDrawable.State toState) {
- switch (toState) {
- case NORMAL:
- switch (fromState) {
- case FAST_SCROLL_HIGHLIGHTED:
- return FastBitmapDrawable.FAST_SCROLL_INACTIVE_DURATION / 4;
- }
- }
- return 0;
+ public int getIconSize() {
+ return mIconSize;
}
/**
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 8d69fe374..8a477d809 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -42,6 +42,7 @@ import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
/**
@@ -142,8 +143,8 @@ public abstract class ButtonDropTarget extends TextView
mCurrentFilter = new ColorMatrix();
}
- DragView.setColorScale(getTextColor(), mSrcFilter);
- DragView.setColorScale(targetColor, mDstFilter);
+ Themes.setColorScaleOnMatrix(getTextColor(), mSrcFilter);
+ Themes.setColorScaleOnMatrix(targetColor, mDstFilter);
ValueAnimator anim1 = ValueAnimator.ofObject(
new FloatArrayEvaluator(mCurrentFilter.getArray()),
mSrcFilter.getArray(), mDstFilter.getArray());
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index cac6c065a..c0946a0e3 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -51,7 +51,7 @@ import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.accessibility.FolderAccessibilityHelper;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.util.CellAndSpan;
@@ -106,7 +106,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
private ArrayList<FolderIcon.PreviewBackground> mFolderBackgrounds = new ArrayList<FolderIcon.PreviewBackground>();
FolderIcon.PreviewBackground mFolderLeaveBehind = new FolderIcon.PreviewBackground();
- Paint mFolderBgPaint = new Paint();
private float mBackgroundAlpha;
@@ -502,9 +501,9 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
canvas.save();
canvas.translate(mTempLocation[0], mTempLocation[1]);
- bg.drawBackground(canvas, mFolderBgPaint);
+ bg.drawBackground(canvas);
if (!bg.isClipping) {
- bg.drawBackgroundStroke(canvas, mFolderBgPaint);
+ bg.drawBackgroundStroke(canvas);
}
canvas.restore();
}
@@ -514,7 +513,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
mFolderLeaveBehind.delegateCellY, mTempLocation);
canvas.save();
canvas.translate(mTempLocation[0], mTempLocation[1]);
- mFolderLeaveBehind.drawLeaveBehind(canvas, mFolderBgPaint);
+ mFolderLeaveBehind.drawLeaveBehind(canvas);
canvas.restore();
}
}
@@ -529,7 +528,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
canvas.save();
canvas.translate(mTempLocation[0], mTempLocation[1]);
- bg.drawBackgroundStroke(canvas, mFolderBgPaint);
+ bg.drawBackgroundStroke(canvas);
canvas.restore();
}
}
@@ -570,7 +569,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
try {
dispatchRestoreInstanceState(states);
} catch (IllegalArgumentException ex) {
- if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD) {
throw ex;
}
// Mismatched viewId / viewType preventing restore. Skip restore on production builds.
diff --git a/src/com/android/launcher3/DeferredHandler.java b/src/com/android/launcher3/DeferredHandler.java
deleted file mode 100644
index a43ab6723..000000000
--- a/src/com/android/launcher3/DeferredHandler.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.MessageQueue;
-
-import com.android.launcher3.util.Thunk;
-
-import java.util.LinkedList;
-
-/**
- * Queue of things to run on a looper thread. Items posted with {@link #post} will not
- * be actually enqued on the handler until after the last one has run, to keep from
- * starving the thread.
- *
- * This class is fifo.
- */
-public class DeferredHandler {
- @Thunk LinkedList<Runnable> mQueue = new LinkedList<>();
- private MessageQueue mMessageQueue = Looper.myQueue();
- private Impl mHandler = new Impl();
-
- @Thunk class Impl extends Handler implements MessageQueue.IdleHandler {
- public void handleMessage(Message msg) {
- Runnable r;
- synchronized (mQueue) {
- if (mQueue.size() == 0) {
- return;
- }
- r = mQueue.removeFirst();
- }
- r.run();
- synchronized (mQueue) {
- scheduleNextLocked();
- }
- }
-
- public boolean queueIdle() {
- handleMessage(null);
- return false;
- }
- }
-
- private class IdleRunnable implements Runnable {
- Runnable mRunnable;
-
- IdleRunnable(Runnable r) {
- mRunnable = r;
- }
-
- public void run() {
- mRunnable.run();
- }
- }
-
- public DeferredHandler() {
- }
-
- /** Schedule runnable to run after everything that's on the queue right now. */
- public void post(Runnable runnable) {
- synchronized (mQueue) {
- mQueue.add(runnable);
- if (mQueue.size() == 1) {
- scheduleNextLocked();
- }
- }
- }
-
- /** Schedule runnable to run when the queue goes idle. */
- public void postIdle(final Runnable runnable) {
- post(new IdleRunnable(runnable));
- }
-
- public void cancelAll() {
- synchronized (mQueue) {
- mQueue.clear();
- }
- }
-
- /** Runs all queued Runnables from the calling thread. */
- public void flush() {
- LinkedList<Runnable> queue = new LinkedList<>();
- synchronized (mQueue) {
- queue.addAll(mQueue);
- mQueue.clear();
- }
- for (Runnable r : queue) {
- r.run();
- }
- }
-
- void scheduleNextLocked() {
- if (mQueue.size() > 0) {
- Runnable peek = mQueue.getFirst();
- if (peek instanceof IdleRunnable) {
- mMessageQueue.addIdleHandler(mHandler);
- } else {
- mHandler.sendEmptyMessage(1);
- }
- }
- }
-}
-
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index c9e3d4f10..43f7d2317 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -32,6 +32,7 @@ import android.widget.FrameLayout;
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.badge.BadgeRenderer;
+import com.android.launcher3.config.FeatureFlags;
import java.util.ArrayList;
@@ -530,10 +531,13 @@ public class DeviceProfile {
workspacePadding.bottom);
workspace.setPageSpacing(getWorkspacePageSpacing());
- View qsbContainer = launcher.getQsbContainer();
- lp = (FrameLayout.LayoutParams) qsbContainer.getLayoutParams();
- lp.topMargin = mInsets.top + workspacePadding.top;
- qsbContainer.setLayoutParams(lp);
+ // Only display when enabled
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ View qsbContainer = launcher.getQsbContainer();
+ lp = (FrameLayout.LayoutParams) qsbContainer.getLayoutParams();
+ lp.topMargin = mInsets.top + workspacePadding.top;
+ qsbContainer.setLayoutParams(lp);
+ }
// Layout the hotseat
Hotseat hotseat = (Hotseat) launcher.findViewById(R.id.hotseat);
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index d05673ccc..596aa8f7b 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -29,6 +29,7 @@ import android.widget.EditText;
public class ExtendedEditText extends EditText {
private boolean mShowImeAfterFirstLayout;
+ private boolean mForceDisableSuggestions = false;
/**
* Implemented by listeners of the back key.
@@ -107,4 +108,17 @@ public class ExtendedEditText extends EditText {
mBackKeyListener.onBackKey();
}
}
+
+ /**
+ * Set to true when you want isSuggestionsEnabled to return false.
+ * Use this to disable the red underlines that appear under typos when suggestions is enabled.
+ */
+ public void forceDisableSuggestions(boolean forceDisableSuggestions) {
+ mForceDisableSuggestions = forceDisableSuggestions;
+ }
+
+ @Override
+ public boolean isSuggestionsEnabled() {
+ return !mForceDisableSuggestions && super.isSuggestionsEnabled();
+ }
}
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 1f74c8877..5a44f7504 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -16,7 +16,6 @@
package com.android.launcher3;
-import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Bitmap;
@@ -32,42 +31,19 @@ import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.util.Property;
import android.util.SparseArray;
-import android.view.animation.DecelerateInterpolator;
import com.android.launcher3.badge.BadgeInfo;
import com.android.launcher3.badge.BadgeRenderer;
import com.android.launcher3.graphics.IconPalette;
public class FastBitmapDrawable extends Drawable {
+
+ private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
+
+ private static final float PRESSED_BRIGHTNESS = 100f / 255f;
private static final float DISABLED_DESATURATION = 1f;
private static final float DISABLED_BRIGHTNESS = 0.5f;
- /**
- * The possible states that a FastBitmapDrawable can be in.
- */
- public enum State {
-
- NORMAL (0f, 0f, 1f, new DecelerateInterpolator()),
- PRESSED (0f, 100f / 255f, 1f, CLICK_FEEDBACK_INTERPOLATOR),
- FAST_SCROLL_HIGHLIGHTED (0f, 0f, 1.15f, new DecelerateInterpolator()),
- FAST_SCROLL_UNHIGHLIGHTED (0f, 0f, 1f, new DecelerateInterpolator());
-
- public final float desaturation;
- public final float brightness;
- /**
- * Used specifically by the view drawing this FastBitmapDrawable.
- */
- public final float viewScale;
- public final TimeInterpolator interpolator;
-
- State(float desaturation, float brightness, float viewScale, TimeInterpolator interpolator) {
- this.desaturation = desaturation;
- this.brightness = brightness;
- this.viewScale = viewScale;
- this.interpolator = interpolator;
- }
- }
-
public static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = new TimeInterpolator() {
@Override
@@ -82,10 +58,6 @@ public class FastBitmapDrawable extends Drawable {
}
};
public static final int CLICK_FEEDBACK_DURATION = 2000;
- public static final int FAST_SCROLL_HIGHLIGHT_DURATION = 225;
- public static final int FAST_SCROLL_UNHIGHLIGHT_DURATION = 150;
- public static final int FAST_SCROLL_UNHIGHLIGHT_FROM_NORMAL_DURATION = 225;
- public static final int FAST_SCROLL_INACTIVE_DURATION = 275;
// Since we don't need 256^2 values for combinations of both the brightness and saturation, we
// reduce the value space to a smaller value V, which reduces the number of cached
@@ -101,7 +73,8 @@ public class FastBitmapDrawable extends Drawable {
protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
private final Bitmap mBitmap;
- private State mState = State.NORMAL;
+
+ private boolean mIsPressed;
private boolean mIsDisabled;
private BadgeInfo mBadgeInfo;
@@ -123,6 +96,19 @@ public class FastBitmapDrawable extends Drawable {
}
};
+ private static final Property<FastBitmapDrawable, Float> BRIGHTNESS
+ = new Property<FastBitmapDrawable, Float>(Float.TYPE, "brightness") {
+ @Override
+ public Float get(FastBitmapDrawable fastBitmapDrawable) {
+ return fastBitmapDrawable.getBrightness();
+ }
+
+ @Override
+ public void set(FastBitmapDrawable fastBitmapDrawable, Float value) {
+ fastBitmapDrawable.setBrightness(value);
+ }
+ };
+
// The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
// as a result, can be used to compose the key for the cached ColorMatrixColorFilters
private int mDesaturation = 0;
@@ -130,8 +116,8 @@ public class FastBitmapDrawable extends Drawable {
private int mAlpha = 255;
private int mPrevUpdateKey = Integer.MAX_VALUE;
- // Animators for the fast bitmap drawable's properties
- private AnimatorSet mPropertyAnimator;
+ // Animators for the fast bitmap drawable's brightness
+ private ObjectAnimator mBrightnessAnimator;
public FastBitmapDrawable(Bitmap b) {
mBitmap = b;
@@ -181,7 +167,7 @@ public class FastBitmapDrawable extends Drawable {
}
}
- public IconPalette getIconPalette() {
+ protected IconPalette getIconPalette() {
if (mIconPalette == null) {
mIconPalette = IconPalette.fromDominantColor(Utilities
.findDominantColorByHue(mBitmap, 20));
@@ -243,60 +229,50 @@ public class FastBitmapDrawable extends Drawable {
return mBitmap;
}
- /**
- * Animates this drawable to a new state.
- *
- * @return whether the state has changed.
- */
- public boolean animateState(State newState) {
- State prevState = mState;
- if (mState != newState) {
- mState = newState;
-
- float desaturation = mIsDisabled ? DISABLED_DESATURATION : newState.desaturation;
- float brightness = mIsDisabled ? DISABLED_BRIGHTNESS: newState.brightness;
-
- mPropertyAnimator = cancelAnimator(mPropertyAnimator);
- mPropertyAnimator = new AnimatorSet();
- mPropertyAnimator.playTogether(
- ObjectAnimator.ofFloat(this, "desaturation", desaturation),
- ObjectAnimator.ofFloat(this, "brightness", brightness));
- mPropertyAnimator.setInterpolator(newState.interpolator);
- mPropertyAnimator.setDuration(getDurationForStateChange(prevState, newState));
- mPropertyAnimator.setStartDelay(getStartDelayForStateChange(prevState, newState));
- mPropertyAnimator.start();
- return true;
- }
- return false;
+ @Override
+ public boolean isStateful() {
+ return true;
}
- /**
- * Immediately sets this drawable to a new state.
- *
- * @return whether the state has changed.
- */
- public boolean setState(State newState) {
- if (mState != newState) {
- mState = newState;
+ @Override
+ protected boolean onStateChange(int[] state) {
+ boolean isPressed = false;
+ for (int s : state) {
+ if (s == android.R.attr.state_pressed) {
+ isPressed = true;
+ break;
+ }
+ }
+ if (mIsPressed != isPressed) {
+ mIsPressed = isPressed;
- mPropertyAnimator = cancelAnimator(mPropertyAnimator);
+ if (mBrightnessAnimator != null) {
+ mBrightnessAnimator.cancel();
+ }
- invalidateDesaturationAndBrightness();
+ if (mIsPressed) {
+ // Animate when going to pressed state
+ mBrightnessAnimator = ObjectAnimator.ofFloat(
+ this, BRIGHTNESS, getExpectedBrightness());
+ mBrightnessAnimator.setDuration(CLICK_FEEDBACK_DURATION);
+ mBrightnessAnimator.setInterpolator(CLICK_FEEDBACK_INTERPOLATOR);
+ mBrightnessAnimator.start();
+ } else {
+ setBrightness(getExpectedBrightness());
+ }
return true;
}
return false;
}
private void invalidateDesaturationAndBrightness() {
- setDesaturation(mIsDisabled ? DISABLED_DESATURATION : mState.desaturation);
- setBrightness(mIsDisabled ? DISABLED_BRIGHTNESS: mState.brightness);
+ setDesaturation(mIsDisabled ? DISABLED_DESATURATION : 0);
+ setBrightness(getExpectedBrightness());
}
- /**
- * Returns the current state.
- */
- public State getCurrentState() {
- return mState;
+ private float getExpectedBrightness() {
+ return mIsDisabled ? DISABLED_BRIGHTNESS :
+ (mIsPressed ? PRESSED_BRIGHTNESS : 0);
}
public void setIsDisabled(boolean isDisabled) {
@@ -307,49 +283,6 @@ public class FastBitmapDrawable extends Drawable {
}
/**
- * Returns the duration for the state change animation.
- */
- public static int getDurationForStateChange(State fromState, State toState) {
- switch (toState) {
- case NORMAL:
- switch (fromState) {
- case PRESSED:
- return 0;
- case FAST_SCROLL_HIGHLIGHTED:
- case FAST_SCROLL_UNHIGHLIGHTED:
- return FAST_SCROLL_INACTIVE_DURATION;
- }
- case PRESSED:
- return CLICK_FEEDBACK_DURATION;
- case FAST_SCROLL_HIGHLIGHTED:
- return FAST_SCROLL_HIGHLIGHT_DURATION;
- case FAST_SCROLL_UNHIGHLIGHTED:
- switch (fromState) {
- case NORMAL:
- // When animating from normal state, take a little longer
- return FAST_SCROLL_UNHIGHLIGHT_FROM_NORMAL_DURATION;
- default:
- return FAST_SCROLL_UNHIGHLIGHT_DURATION;
- }
- }
- return 0;
- }
-
- /**
- * Returns the start delay when animating between certain fast scroll states.
- */
- public static int getStartDelayForStateChange(State fromState, State toState) {
- switch (toState) {
- case FAST_SCROLL_UNHIGHLIGHTED:
- switch (fromState) {
- case NORMAL:
- return FAST_SCROLL_UNHIGHLIGHT_DURATION / 4;
- }
- }
- return 0;
- }
-
- /**
* Sets the saturation of this icon, 0 [full color] -> 1 [desaturated]
*/
private void setDesaturation(float desaturation) {
@@ -435,11 +368,4 @@ public class FastBitmapDrawable extends Drawable {
}
invalidateSelf();
}
-
- private AnimatorSet cancelAnimator(AnimatorSet animator) {
- if (animator != null) {
- animator.cancel();
- }
- return null;
- }
}
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index b36734bab..fe7acda17 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -22,7 +22,7 @@ import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewGroup;
-import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderPagedView;
import com.android.launcher3.util.FocusLogic;
@@ -93,7 +93,7 @@ public class FocusHelper {
}
if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
- if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD) {
throw new IllegalStateException("Parent of the focused item is not supported.");
} else {
return false;
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 2c69d0a69..0041bb4d6 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -114,11 +114,18 @@ public class FolderInfo extends ItemInfo {
}
}
+ public void prepareAutoUpdate() {
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.get(i).prepareAutoUpdate();
+ }
+ }
+
public interface FolderListener {
public void onAdd(ShortcutInfo item);
public void onRemove(ShortcutInfo item);
public void onTitleChanged(CharSequence title);
public void onItemsChanged(boolean animate);
+ public void prepareAutoUpdate();
}
public boolean hasOption(int optionFlag) {
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 1bab77449..80dec1670 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -218,6 +218,10 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId, context), context);
}
+ public static void queueActivityInfo(LauncherActivityInfo activity, Context context) {
+ queuePendingShortcutInfo(new PendingInstallShortcutInfo(activity, context), context);
+ }
+
public static HashSet<ShortcutKey> getPendingShortcuts(Context context) {
HashSet<ShortcutKey> result = new HashSet<>();
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 8aeab8712..146c2eee7 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -28,7 +28,6 @@ import android.view.Display;
import android.view.WindowManager;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.Thunk;
@@ -335,7 +334,7 @@ public class InvariantDeviceProfile {
}
public int getAllAppsButtonRank() {
- if (ProviderConfig.IS_DOGFOOD_BUILD && FeatureFlags.NO_ALL_APPS_ICON) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD && FeatureFlags.NO_ALL_APPS_ICON) {
throw new IllegalAccessError("Accessing all apps rank when all-apps is disabled");
}
return numHotseatIcons / 2;
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index a3d8c6a9d..1e020e258 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -35,7 +35,9 @@ public abstract class ItemInfoWithIcon extends ItemInfo {
protected ItemInfoWithIcon() { }
- protected ItemInfoWithIcon(ItemInfo info) {
+ protected ItemInfoWithIcon(ItemInfoWithIcon info) {
super(info);
+ iconBitmap = info.iconBitmap;
+ usingLowResIcon = info.usingLowResIcon;
}
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c5cefa678..a69f501d7 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -72,6 +72,7 @@ import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.OvershootInterpolator;
@@ -86,15 +87,11 @@ import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DefaultAppSearchController;
import com.android.launcher3.anim.AnimationLayerSet;
-import com.android.launcher3.model.ModelWriter;
-import com.android.launcher3.notification.NotificationListener;
-import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PinItemRequestCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
@@ -107,10 +104,13 @@ import com.android.launcher3.keyboard.CustomActionsPopup;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -120,7 +120,6 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.util.ActivityResultInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.LogConfig;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
@@ -176,6 +175,13 @@ public class Launcher extends BaseActivity
*/
protected static final int REQUEST_LAST = 100;
+ private static final int SOFT_INPUT_MODE_DEFAULT =
+ WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
+ | WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
+ private static final int SOFT_INPUT_MODE_ALL_APPS =
+ WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
+ | WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
+
// The Intent extra that defines whether to ignore the launch animation
static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
"com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
@@ -316,8 +322,6 @@ public class Launcher extends BaseActivity
*/
private PendingRequestArgs mPendingRequestArgs;
- private UserEventDispatcher mUserEventDispatcher;
-
private float mLastDispatchTouchEventX = 0.0f;
public ViewGroupFocusHelper mFocusHandler;
@@ -628,23 +632,6 @@ public class Launcher extends BaseActivity
}
}
- public UserEventDispatcher getUserEventDispatcher() {
- if (mLauncherCallbacks != null) {
- UserEventDispatcher dispatcher = mLauncherCallbacks.getUserEventDispatcher();
- if (dispatcher != null) {
- return dispatcher;
- }
- }
-
- // Logger object is a singleton and does not have to be coupled with the foreground
- // activity. Since most user event logging is done on the UI, the object is retrieved
- // from the callback for convenience.
- if (mUserEventDispatcher == null) {
- mUserEventDispatcher = new UserEventDispatcher();
- }
- return mUserEventDispatcher;
- }
-
public boolean isDraggingEnabled() {
// We prevent dragging when we are loading the workspace as it is possible to pick up a view
// that is subsequently removed from the workspace in startBinding().
@@ -1072,6 +1059,7 @@ public class Launcher extends BaseActivity
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onResume();
}
+
}
@Override
@@ -1164,9 +1152,10 @@ public class Launcher extends BaseActivity
if (mLauncherCallbacks != null) {
return mLauncherCallbacks.hasSettings();
} else {
- // On devices with a locked orientation, we will at least have the allow rotation
- // setting.
- return !getResources().getBoolean(R.bool.allow_rotation);
+ // On O and above we there is always some setting present settings (add icon to
+ // home screen or icon badging). On earlier APIs we will have the allow rotation
+ // setting, on devices with a locked orientation,
+ return Utilities.isAtLeastO() || !getResources().getBoolean(R.bool.allow_rotation);
}
}
@@ -2472,7 +2461,7 @@ public class Launcher extends BaseActivity
throw new IllegalArgumentException("Input must have a valid intent");
}
boolean success = startActivitySafely(v, intent, item);
- getUserEventDispatcher().logAppLaunch(v, intent);
+ getUserEventDispatcher().logAppLaunch(v, intent); // TODO for discovered apps b/35802115
if (success && v instanceof BubbleTextView) {
mWaitingForResume = (BubbleTextView) v;
@@ -2721,9 +2710,10 @@ public class Launcher extends BaseActivity
intent.setSourceBounds(getViewBounds(v));
}
try {
- if (Utilities.ATLEAST_MARSHMALLOW && item != null
+ if (Utilities.ATLEAST_MARSHMALLOW
+ && (item instanceof ShortcutInfo)
&& (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
- || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+ || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
&& !((ShortcutInfo) item).isPromise()) {
// Shortcuts need some special checks due to legacy reasons.
startShortcutIntentSafely(intent, optsBundle, item);
@@ -2896,7 +2886,7 @@ public class Launcher extends BaseActivity
}
// Change the state *after* we've called all the transition code
- mState = State.WORKSPACE;
+ setState(State.WORKSPACE);
if (changed) {
// Send an accessibility event to announce the context change
@@ -2933,12 +2923,30 @@ public class Launcher extends BaseActivity
mWorkspace.setVisibility(View.VISIBLE);
mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
Workspace.State.OVERVIEW, animated, postAnimRunnable);
- mState = State.WORKSPACE;
+ setState(State.WORKSPACE);
+
// If animated from long press, then don't allow any of the controller in the drag
// layer to intercept any remaining touch.
mWorkspace.requestDisallowInterceptTouchEvent(animated);
}
+ private void setState(State state) {
+ this.mState = state;
+ updateSoftInputMode();
+ }
+
+ private void updateSoftInputMode() {
+ if (FeatureFlags.LAUNCHER3_UPDATE_SOFT_INPUT_MODE) {
+ final int mode;
+ if (isAppsViewVisible()) {
+ mode = SOFT_INPUT_MODE_ALL_APPS;
+ } else {
+ mode = SOFT_INPUT_MODE_DEFAULT;
+ }
+ getWindow().setSoftInputMode(mode);
+ }
+ }
+
/**
* Shows the apps view.
*/
@@ -3000,7 +3008,7 @@ public class Launcher extends BaseActivity
}
// Change the state *after* we've called all the transition code
- mState = toState;
+ setState(toState);
AbstractFloatingView.closeAllOpenViews(this);
// Send an accessibility event to announce the context change
@@ -3030,7 +3038,7 @@ public class Launcher extends BaseActivity
mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
Workspace.State.SPRING_LOADED, true /* animated */,
null /* onCompleteRunnable */);
- mState = State.WORKSPACE_SPRING_LOADED;
+ setState(State.WORKSPACE_SPRING_LOADED);
}
public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
@@ -3210,6 +3218,9 @@ public class Launcher extends BaseActivity
if (LauncherAppState.PROFILE_STARTUP) {
Trace.beginSection("Starting page bind");
}
+
+ AbstractFloatingView.closeAllOpenViews(this);
+
setWorkspaceLoading(true);
// Clear the workspace because it's going to be rebound
@@ -3376,7 +3387,7 @@ public class Launcher extends BaseActivity
Object tag = v.getTag();
String desc = "Collision while binding workspace item: " + item
+ ". Collides with " + tag;
- if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD) {
throw (new RuntimeException(desc));
} else {
Log.d(TAG, desc);
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 43cf827da..aa7f5ee5f 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -130,17 +130,4 @@ public class LauncherAnimUtils {
return anim;
}
- public static ValueAnimator animateViewHeight(final View v, int fromHeight, int toHeight) {
- ValueAnimator anim = ValueAnimator.ofInt(fromHeight, toHeight);
- anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- int val = (Integer) valueAnimator.getAnimatedValue();
- ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
- layoutParams.height = val;
- v.setLayoutParams(layoutParams);
- }
- });
- return anim;
- }
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index e0e53a647..f2d66fe81 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -26,7 +26,7 @@ import android.util.Log;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dynamicui.ExtractionUtils;
import com.android.launcher3.model.GridSizeMigrationTask;
import com.android.launcher3.util.ConfigMonitor;
@@ -38,7 +38,7 @@ import java.util.concurrent.ExecutionException;
public class LauncherAppState {
- public static final boolean PROFILE_STARTUP = ProviderConfig.IS_DOGFOOD_BUILD;
+ public static final boolean PROFILE_STARTUP = FeatureFlags.IS_DOGFOOD_BUILD;
// We do not need any synchronization for this variable as its only written on UI thread.
private static LauncherAppState INSTANCE;
@@ -134,15 +134,6 @@ public class LauncherAppState {
PackageInstallerCompat.getInstance(mContext).onStop();
}
- /**
- * Reloads the workspace items from the DB and re-binds the workspace. This should generally
- * not be called as DB updates are automatically followed by UI update
- */
- public void reloadWorkspace() {
- mModel.resetLoadedState(false, true);
- mModel.startLoaderFromBackground();
- }
-
LauncherModel setLauncher(Launcher launcher) {
getLocalProvider(mContext).setLauncherProviderChangeListener(launcher);
mModel.initialize(launcher);
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 6394b9052..2bac11f97 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -92,7 +92,6 @@ public interface LauncherCallbacks {
/*
* Extensions points for adding / replacing some other aspects of the Launcher experience.
*/
- public UserEventDispatcher getUserEventDispatcher();
public boolean shouldMoveToDefaultScreenOnHomeIntent();
public boolean hasSettings();
public AllAppsSearchBarController getAllAppsSearchBarController();
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 34d576d27..35811d38a 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -26,7 +26,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.LauncherActivityInfo;
-import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
@@ -45,10 +44,11 @@ import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dynamicui.ExtractionUtils;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.FolderIconPreviewVerifier;
import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.AddWorkspaceItemsTask;
@@ -72,8 +72,7 @@ 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.ContentWriter;
-import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LooperIdleLock;
import com.android.launcher3.util.ManagedProfileHeuristic;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper;
@@ -85,7 +84,6 @@ import com.android.launcher3.util.ViewOnDrawExecutor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -95,6 +93,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
/**
@@ -112,9 +111,9 @@ public class LauncherModel extends BroadcastReceiver
private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
private static final long INVALID_SCREEN_ID = -1L;
+ private final MainThreadExecutor mUiExecutor = new MainThreadExecutor();
@Thunk final LauncherAppState mApp;
@Thunk final Object mLock = new Object();
- @Thunk DeferredHandler mHandler = new DeferredHandler();
@Thunk LoaderTask mLoaderTask;
@Thunk boolean mIsLoaderTaskRunning;
@Thunk boolean mHasLoaderCompletedOnce;
@@ -125,12 +124,16 @@ public class LauncherModel extends BroadcastReceiver
}
@Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
- // We start off with everything not loaded. After that, we assume that
+ // Indicates whether the current model data is valid or not.
+ // We start off with everything not loaded. After that, we assume that
// our monitoring of the package manager provides all updates and we never
- // need to do a requery. These are only ever touched from the loader thread.
- private boolean mWorkspaceLoaded;
- private boolean mAllAppsLoaded;
- private boolean mDeepShortcutsLoaded;
+ // need to do a requery. This is only ever touched from the loader thread.
+ private boolean mModelLoaded;
+ public boolean isModelLoaded() {
+ synchronized (mLock) {
+ return mModelLoaded && mLoaderTask == null;
+ }
+ }
/**
* Set of runnables to be called on the background thread after the workspace binding
@@ -150,11 +153,11 @@ public class LauncherModel extends BroadcastReceiver
private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
@Override
public void run() {
- if (mDeepShortcutsLoaded) {
+ if (mModelLoaded) {
boolean hasShortcutHostPermission =
DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
if (hasShortcutHostPermission != mHasShortcutHostPermission) {
- mApp.reloadWorkspace();
+ forceReload();
}
}
}
@@ -216,17 +219,6 @@ public class LauncherModel extends BroadcastReceiver
mUserManager = UserManagerCompat.getInstance(context);
}
- /** Runs the specified runnable immediately if called from the main thread, otherwise it is
- * posted on the main thread handler. */
- private void runOnMainThread(Runnable r) {
- if (sWorkerThread.getThreadId() == Process.myTid()) {
- // If we are on the worker thread, post onto the main handler
- mHandler.post(r);
- } else {
- r.run();
- }
- }
-
/** Runs the specified runnable immediately if called from the worker thread, otherwise it is
* posted on the worker thread handler. */
private static void runOnWorkerThread(Runnable r) {
@@ -376,8 +368,6 @@ public class LauncherModel extends BroadcastReceiver
public void initialize(Callbacks callbacks) {
synchronized (mLock) {
Preconditions.assertUIThread();
- // Remove any queued UI runnables
- mHandler.cancelAll();
mCallbacks = new WeakReference<>(callbacks);
}
}
@@ -482,8 +472,16 @@ public class LauncherModel extends BroadcastReceiver
}
}
- void forceReload() {
- resetLoadedState(true, true);
+ /**
+ * Reloads the workspace items from the DB and re-binds the workspace. This should generally
+ * not be called as DB updates are automatically followed by UI update
+ */
+ public void forceReload() {
+ synchronized (mLock) {
+ // Stop any existing loaders first, so they don't set mModelLoaded to true later
+ stopLoaderLocked();
+ mModelLoaded = false;
+ }
// Do this here because if the launcher activity is running it will be restarted.
// If it's not running startLoaderFromBackground will merely tell it that it needs
@@ -491,19 +489,6 @@ public class LauncherModel extends BroadcastReceiver
startLoaderFromBackground();
}
- public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
- synchronized (mLock) {
- // Stop any existing loaders first, so they don't set mAllAppsLoaded or
- // mWorkspaceLoaded to true later
- stopLoaderLocked();
- if (resetAllAppsLoaded) mAllAppsLoaded = false;
- if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
- // Always reset deep shortcuts loaded.
- // TODO: why?
- mDeepShortcutsLoaded = false;
- }
- }
-
/**
* When the launcher is in the background, it's possible for it to miss paired
* configuration changes. So whenever we trigger the loader from the background
@@ -546,18 +531,17 @@ public class LauncherModel extends BroadcastReceiver
if (mCallbacks != null && mCallbacks.get() != null) {
final Callbacks oldCallbacks = mCallbacks.get();
// Clear any pending bind-runnables from the synchronized load process.
- runOnMainThread(new Runnable() {
- public void run() {
- oldCallbacks.clearPendingBinds();
- }
- });
+ mUiExecutor.execute(new Runnable() {
+ public void run() {
+ oldCallbacks.clearPendingBinds();
+ }
+ });
// If there is already one running, tell it to stop.
stopLoaderLocked();
mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
- // TODO: mDeepShortcutsLoaded does not need to be true for synchronous bind.
- if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded
- && mWorkspaceLoaded && mDeepShortcutsLoaded && !mIsLoaderTaskRunning) {
+ if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
+ && mModelLoaded && !mIsLoaderTaskRunning) {
mLoaderTask.runBindSynchronousPage(synchronousBindPage);
return true;
} else {
@@ -602,68 +586,21 @@ public class LauncherModel extends BroadcastReceiver
@Thunk boolean mIsLoadingAndBindingWorkspace;
private boolean mStopped;
- @Thunk boolean mLoadAndBindStepFinished;
LoaderTask(Context context, int pageToBindFirst) {
mContext = context;
mPageToBindFirst = pageToBindFirst;
}
- private void loadAndBindWorkspace() {
- mIsLoadingAndBindingWorkspace = true;
-
- // Load the workspace
- if (DEBUG_LOADERS) {
- Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
- }
-
- if (!mWorkspaceLoaded) {
- loadWorkspace();
- synchronized (LoaderTask.this) {
- if (mStopped) {
- return;
- }
- mWorkspaceLoaded = true;
- }
- }
-
- // Bind the workspace
- bindWorkspace(mPageToBindFirst);
- }
-
private void waitForIdle() {
// Wait until the either we're stopped or the other threads are done.
// This way we don't start loading all apps until the workspace has settled
// down.
synchronized (LoaderTask.this) {
- final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
-
- mHandler.postIdle(new Runnable() {
- public void run() {
- synchronized (LoaderTask.this) {
- mLoadAndBindStepFinished = true;
- if (DEBUG_LOADERS) {
- Log.d(TAG, "done with previous binding step");
- }
- LoaderTask.this.notify();
- }
- }
- });
-
- while (!mStopped && !mLoadAndBindStepFinished) {
- try {
- // Just in case mFlushingWorkerThread changes but we aren't woken up,
- // wait no longer than 1sec at a time
- this.wait(1000);
- } catch (InterruptedException ex) {
- // Ignore
- }
- }
- if (DEBUG_LOADERS) {
- Log.d(TAG, "waited "
- + (SystemClock.uptimeMillis()-workspaceWaitTime)
- + "ms for previous step to finish binding");
- }
+ LooperIdleLock idleLock = new LooperIdleLock(this, Looper.getMainLooper());
+ // Just in case mFlushingWorkerThread changes but we aren't woken up,
+ // wait no longer than 1sec at a time
+ while (!mStopped && idleLock.awaitLocked(1000));
}
}
@@ -673,7 +610,7 @@ public class LauncherModel extends BroadcastReceiver
throw new RuntimeException("Should not call runBindSynchronousPage() without " +
"valid page index");
}
- if (!mAllAppsLoaded || !mWorkspaceLoaded) {
+ if (!mModelLoaded) {
// Ensure that we don't try and bind a specified page when the pages have not been
// loaded already (we should load everything asynchronously in that case)
throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
@@ -686,15 +623,6 @@ public class LauncherModel extends BroadcastReceiver
}
}
- // XXX: Throw an exception if we are already loading (since we touch the worker thread
- // data structures, we can't allow any other thread to touch that data, but because
- // this call is synchronous, we can get away with not locking).
-
- // The LauncherModel is static in the LauncherAppState and mHandler may have queued
- // operations from the previous activity. We need to ensure that all queued operations
- // are executed before any synchronous binding work is done.
- mHandler.flush();
-
// Divide the set of loaded items into those that we are binding synchronously, and
// everything else that is to be bound normally (asynchronously).
bindWorkspace(synchronousBindPage);
@@ -705,6 +633,14 @@ public class LauncherModel extends BroadcastReceiver
bindDeepShortcuts();
}
+ private void verifyNotStopped() throws CancellationException {
+ synchronized (LoaderTask.this) {
+ if (mStopped) {
+ throw new CancellationException("Loader stopped");
+ }
+ }
+ }
+
public void run() {
synchronized (mLock) {
if (mStopped) {
@@ -712,41 +648,71 @@ public class LauncherModel extends BroadcastReceiver
}
mIsLoaderTaskRunning = true;
}
- // Optimize for end-user experience: if the Launcher is up and // running with the
- // All Apps interface in the foreground, load All Apps first. Otherwise, load the
- // workspace first (default).
- keep_running: {
- if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
- loadAndBindWorkspace();
- if (mStopped) {
- break keep_running;
- }
+ try {
+ long now = 0;
+ if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace");
+ // Set to false in bindWorkspace()
+ mIsLoadingAndBindingWorkspace = true;
+ loadWorkspace();
+
+ verifyNotStopped();
+ if (DEBUG_LOADERS) Log.d(TAG, "step 1.2: bind workspace workspace");
+ bindWorkspace(mPageToBindFirst);
+ // Take a break
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "step 1 completed, wait for idle");
+ now = SystemClock.uptimeMillis();
+ }
waitForIdle();
+ if (DEBUG_LOADERS) Log.d(TAG, "Waited " + (SystemClock.uptimeMillis() - now) + "ms");
+ verifyNotStopped();
// second step
- if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
- loadAndBindAllApps();
+ if (DEBUG_LOADERS) Log.d(TAG, "step 2.1: loading all apps");
+ loadAllApps();
+
+ verifyNotStopped();
+ if (DEBUG_LOADERS) Log.d(TAG, "step 2.2: Update icon cache");
+ updateIconCache();
+ // Take a break
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "step 2 completed, wait for idle");
+ now = SystemClock.uptimeMillis();
+ }
waitForIdle();
+ if (DEBUG_LOADERS) Log.d(TAG, "Waited " + (SystemClock.uptimeMillis() - now) + "ms");
+ verifyNotStopped();
// third step
- if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
- loadAndBindDeepShortcuts();
- }
+ if (DEBUG_LOADERS) Log.d(TAG, "step 3.1: loading deep shortcuts");
+ loadDeepShortcuts();
- // Clear out this reference, otherwise we end up holding it until all of the
- // callback runnables are done.
- mContext = null;
+ verifyNotStopped();
+ if (DEBUG_LOADERS) Log.d(TAG, "step 3.2: bind deep shortcuts");
+ bindDeepShortcuts();
- synchronized (mLock) {
- // If we are still the last one to be scheduled, remove ourselves.
- if (mLoaderTask == this) {
- mLoaderTask = null;
+ synchronized (mLock) {
+ // Everything loaded bind the data.
+ mModelLoaded = true;
+ mHasLoaderCompletedOnce = true;
+ }
+ } catch (CancellationException e) {
+ // Loader stopped, ignore
+ } finally {
+ // Clear out this reference, otherwise we end up holding it until all of the
+ // callback runnables are done.
+ mContext = null;
+
+ synchronized (mLock) {
+ // If we are still the last one to be scheduled, remove ourselves.
+ if (mLoaderTask == this) {
+ mLoaderTask = null;
+ }
+ mIsLoaderTaskRunning = false;
}
- mIsLoaderTaskRunning = false;
- mHasLoaderCompletedOnce = true;
}
}
@@ -818,7 +784,7 @@ public class LauncherModel extends BroadcastReceiver
if (clearDb) {
Log.d(TAG, "loadWorkspace: resetting launcher database");
LauncherSettings.Settings.call(contentResolver,
- LauncherSettings.Settings.METHOD_DELETE_DB);
+ LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
}
Log.d(TAG, "loadWorkspace: loading default favorites");
@@ -886,6 +852,8 @@ public class LauncherModel extends BroadcastReceiver
Intent intent;
String targetPkg;
+ FolderIconPreviewVerifier verifier =
+ new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
while (!mStopped && c.moveToNext()) {
try {
if (c.user == null) {
@@ -984,7 +952,7 @@ public class LauncherModel extends BroadcastReceiver
c.markDeleted("Unrestored app removed: " + targetPkg);
continue;
}
- } else if (pmHelper.isAppOnSdcard(targetPkg)) {
+ } else if (pmHelper.isAppOnSdcard(targetPkg, c.user)) {
// Package is present but not available.
disabledState |= ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
// Add the icon on the workspace anyway.
@@ -1010,7 +978,7 @@ public class LauncherModel extends BroadcastReceiver
}
boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() &&
- c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
+ !verifier.isItemInPreview(c.getInt(rankIndex));
if (c.restoreFlag != 0) {
// Already verified above that user is same as default user
@@ -1034,6 +1002,10 @@ public class LauncherModel extends BroadcastReceiver
info = new ShortcutInfo(pinnedShortcut, context);
info.iconBitmap = LauncherIcons
.createShortcutIcon(pinnedShortcut, context);
+ if (pmHelper.isAppSuspended(
+ pinnedShortcut.getPackage(), info.user)) {
+ info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+ }
intent = info.intent;
} else {
// Create a shortcut info in disabled mode for now.
@@ -1044,7 +1016,7 @@ public class LauncherModel extends BroadcastReceiver
info = c.loadSimpleShortcut();
// Shortcuts are only available on the primary profile
- if (pmHelper.isAppSuspended(targetPkg)) {
+ if (pmHelper.isAppSuspended(targetPkg, c.user)) {
disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
}
@@ -1258,17 +1230,23 @@ public class LauncherModel extends BroadcastReceiver
}
}
- // Sort all the folder items and make sure the first 3 items are high resolution.
+ FolderIconPreviewVerifier verifier =
+ new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
+ // Sort the folder items and make sure all items in the preview are high resolution.
for (FolderInfo folder : sBgDataModel.folders) {
Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
- int pos = 0;
+ verifier.setFolderInfo(folder);
+
+ int numItemsInPreview = 0;
for (ShortcutInfo info : folder.contents) {
- if (info.usingLowResIcon &&
- info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ if (info.usingLowResIcon
+ && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+ && verifier.isItemInPreview(info.rank)) {
mIconCache.getTitleAndIcon(info, false);
+ numItemsInPreview++;
}
- pos ++;
- if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
+
+ if (numItemsInPreview >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
break;
}
}
@@ -1393,7 +1371,7 @@ public class LauncherModel extends BroadcastReceiver
return Utilities.longCompare(lhs.screenId, rhs.screenId);
}
default:
- if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD) {
throw new RuntimeException("Unexpected container type when " +
"sorting workspace items.");
}
@@ -1418,7 +1396,7 @@ public class LauncherModel extends BroadcastReceiver
}
}
};
- runOnMainThread(r);
+ mUiExecutor.execute(r);
}
private void bindWorkspaceItems(final Callbacks oldCallbacks,
@@ -1524,11 +1502,11 @@ public class LauncherModel extends BroadcastReceiver
}
}
};
- runOnMainThread(r);
+ mUiExecutor.execute(r);
bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
- Executor mainExecutor = new DeferredMainThreadExecutor();
+ Executor mainExecutor = mUiExecutor;
// Load items on the current page.
bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor);
@@ -1538,7 +1516,7 @@ public class LauncherModel extends BroadcastReceiver
// This ensures that the first screen is immediately visible (eg. during rotation)
// In case of !validFirstPage, bind all pages one after other.
final Executor deferredExecutor =
- validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor;
+ validFirstPage ? new ViewOnDrawExecutor(mUiExecutor) : mainExecutor;
mainExecutor.execute(new Runnable() {
@Override
@@ -1598,30 +1576,7 @@ public class LauncherModel extends BroadcastReceiver
}
}
};
- runOnMainThread(r);
- }
- }
-
- private void loadAndBindAllApps() {
- if (DEBUG_LOADERS) {
- Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
- }
- if (!mAllAppsLoaded) {
- loadAllApps();
- synchronized (LoaderTask.this) {
- if (mStopped) {
- return;
- }
- }
- updateIconCache();
- synchronized (LoaderTask.this) {
- if (mStopped) {
- return;
- }
- mAllAppsLoaded = true;
- }
- } else {
- onlyBindAllApps();
+ mUiExecutor.execute(r);
}
}
@@ -1671,7 +1626,7 @@ public class LauncherModel extends BroadcastReceiver
}
}
};
- runOnMainThread(r);
+ mUiExecutor.execute(r);
}
private void loadAllApps() {
@@ -1719,21 +1674,21 @@ public class LauncherModel extends BroadcastReceiver
heuristic.processUserApps(apps);
}
};
- runOnMainThread(new Runnable() {
-
- @Override
- public void run() {
- // Check isLoadingWorkspace on the UI thread, as it is updated on
- // the UI thread.
- if (mIsLoadingAndBindingWorkspace) {
- synchronized (mBindCompleteRunnables) {
- mBindCompleteRunnables.add(r);
- }
- } else {
- runOnWorkerThread(r);
- }
- }
- });
+ mUiExecutor.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ // Check isLoadingWorkspace on the UI thread, as it is updated on
+ // the UI thread.
+ if (mIsLoadingAndBindingWorkspace) {
+ synchronized (mBindCompleteRunnables) {
+ mBindCompleteRunnables.add(r);
+ }
+ } else {
+ runOnWorkerThread(r);
+ }
+ }
+ });
}
}
// Huh? Shouldn't this be inside the Runnable below?
@@ -1741,7 +1696,7 @@ public class LauncherModel extends BroadcastReceiver
mBgAllAppsList.added = new ArrayList<AppInfo>();
// Post callback on main thread
- mHandler.post(new Runnable() {
+ mUiExecutor.execute(new Runnable() {
public void run() {
final long bindTime = SystemClock.uptimeMillis();
@@ -1765,11 +1720,8 @@ public class LauncherModel extends BroadcastReceiver
}
}
- private void loadAndBindDeepShortcuts() {
- if (DEBUG_LOADERS) {
- Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded);
- }
- if (!mDeepShortcutsLoaded) {
+ private void loadDeepShortcuts() {
+ if (!mModelLoaded) {
sBgDataModel.deepShortcutMap.clear();
DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext);
mHasShortcutHostPermission = shortcutManager.hasHostPermission();
@@ -1782,14 +1734,7 @@ public class LauncherModel extends BroadcastReceiver
}
}
}
- synchronized (LoaderTask.this) {
- if (mStopped) {
- return;
- }
- mDeepShortcutsLoaded = true;
- }
}
- bindDeepShortcuts();
}
}
@@ -1805,7 +1750,7 @@ public class LauncherModel extends BroadcastReceiver
}
}
};
- runOnMainThread(r);
+ mUiExecutor.execute(r);
}
/**
@@ -1831,6 +1776,12 @@ public class LauncherModel extends BroadcastReceiver
}
void enqueueModelUpdateTask(BaseModelUpdateTask task) {
+ if (!mModelLoaded && mLoaderTask == null) {
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "enqueueModelUpdateTask Ignoring task since loader is pending=" + task);
+ }
+ return;
+ }
task.init(this);
runOnWorkerThread(task);
}
@@ -1850,12 +1801,12 @@ public class LauncherModel extends BroadcastReceiver
public static abstract class BaseModelUpdateTask implements Runnable {
private LauncherModel mModel;
- private DeferredHandler mUiHandler;
+ private Executor mUiExecutor;
/* package private */
void init(LauncherModel model) {
mModel = model;
- mUiHandler = mModel.mHandler;
+ mUiExecutor = mModel.mUiExecutor;
}
@Override
@@ -1878,7 +1829,7 @@ public class LauncherModel extends BroadcastReceiver
*/
public final void scheduleCallbackTask(final CallbackTask task) {
final Callbacks callbacks = mModel.getCallback();
- mUiHandler.post(new Runnable() {
+ mUiExecutor.execute(new Runnable() {
public void run() {
Callbacks cb = mModel.getCallback();
if (callbacks == cb && cb != null) {
@@ -1923,7 +1874,7 @@ public class LauncherModel extends BroadcastReceiver
private void bindWidgetsModel(final Callbacks callbacks) {
final MultiHashMap<PackageItemInfo, WidgetItem> widgets
= mBgWidgetsModel.getWidgetsMap().clone();
- mHandler.post(new Runnable() {
+ mUiExecutor.execute(new Runnable() {
@Override
public void run() {
Callbacks cb = getCallback();
@@ -1980,14 +1931,6 @@ public class LauncherModel extends BroadcastReceiver
}
}
- @Thunk class DeferredMainThreadExecutor implements Executor {
-
- @Override
- public void execute(Runnable command) {
- runOnMainThread(command);
- }
- }
-
/**
* @return the looper for the worker thread which can be used to start background tasks.
*/
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index e71ef2ce5..b83ddb927 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -53,7 +53,6 @@ import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.WorkspaceScreens;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.dynamicui.ExtractionUtils;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.provider.LauncherDbUtils;
@@ -63,6 +62,8 @@ import com.android.launcher3.util.NoLocaleSqliteContext;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Thunk;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
@@ -74,7 +75,7 @@ public class LauncherProvider extends ContentProvider {
private static final int DATABASE_VERSION = 27;
- public static final String AUTHORITY = ProviderConfig.AUTHORITY;
+ public static final String AUTHORITY = (BuildConfig.APPLICATION_ID + ".settings").intern();
static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
@@ -85,9 +86,21 @@ public class LauncherProvider extends ContentProvider {
protected DatabaseHelper mOpenHelper;
+ /**
+ * $ adb shell dumpsys activity provider com.android.launcher3
+ */
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+ if (appState == null || !appState.getModel().isModelLoaded()) {
+ return;
+ }
+ appState.getModel().dumpState("", fd, writer, args);
+ }
+
@Override
public boolean onCreate() {
- if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD) {
Log.d(TAG, "Launcher process started");
}
mListenerHandler = new Handler(mListenerWrapper);
@@ -174,7 +187,7 @@ public class LauncherProvider extends ContentProvider {
if (Utilities.ATLEAST_MARSHMALLOW && Binder.getCallingPid() != Process.myPid()) {
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app != null) {
- app.reloadWorkspace();
+ app.getModel().forceReload();
}
}
}
@@ -205,7 +218,7 @@ public class LauncherProvider extends ContentProvider {
// Deprecated behavior to support legacy devices which rely on provider callbacks.
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app != null && "true".equals(uri.getQueryParameter("isExternalAdd"))) {
- app.reloadWorkspace();
+ app.getModel().forceReload();
}
String notify = uri.getQueryParameter("notify");
@@ -406,18 +419,13 @@ public class LauncherProvider extends ContentProvider {
return result;
}
case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: {
- createEmptyDB();
+ mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
return null;
}
case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
loadDefaultFavoritesIfNecessary();
return null;
}
- case LauncherSettings.Settings.METHOD_DELETE_DB: {
- // Are you sure? (y/n)
- mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
- return null;
- }
}
return null;
}
@@ -469,13 +477,6 @@ public class LauncherProvider extends ContentProvider {
values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
}
- /**
- * Clears all the data for a fresh start.
- */
- synchronized private void createEmptyDB() {
- mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
- }
-
private void clearFlagEmptyDbCreated() {
Utilities.getPrefs(getContext()).edit().remove(EMPTY_DATABASE_CREATED).commit();
}
@@ -518,12 +519,12 @@ public class LauncherProvider extends ContentProvider {
// There might be some partially restored DB items, due to buggy restore logic in
// previous versions of launcher.
- createEmptyDB();
+ mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
// Populate favorites table with initial favorites
if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
&& usingExternallyProvidedLayout) {
// Unable to load external layout. Cleanup and load the internal layout.
- createEmptyDB();
+ mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
getDefaultLayoutParser(widgetHost));
}
@@ -825,9 +826,15 @@ public class LauncherProvider extends ContentProvider {
* Clears all the data for a fresh start.
*/
public void createEmptyDB(SQLiteDatabase db) {
- db.execSQL("DROP TABLE IF EXISTS " + Favorites.TABLE_NAME);
- db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME);
- onCreate(db);
+ db.beginTransaction();
+ try {
+ db.execSQL("DROP TABLE IF EXISTS " + Favorites.TABLE_NAME);
+ db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME);
+ onCreate(db);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
}
/**
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index e450785c7..e8e0eb234 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -22,8 +22,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.provider.BaseColumns;
-import com.android.launcher3.config.ProviderConfig;
-
/**
* Settings related utilities.
*/
@@ -101,7 +99,7 @@ public class LauncherSettings {
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI = Uri.parse("content://" +
- ProviderConfig.AUTHORITY + "/" + TABLE_NAME);
+ LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
/**
* The rank of this screen -- ie. how it is ordered relative to the other screens.
@@ -121,7 +119,7 @@ public class LauncherSettings {
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI = Uri.parse("content://" +
- ProviderConfig.AUTHORITY + "/" + TABLE_NAME);
+ LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
/**
* The content:// style URL for a given row, identified by its id.
@@ -131,7 +129,7 @@ public class LauncherSettings {
* @return The unique content URL for the specified row.
*/
public static Uri getContentUri(long id) {
- return Uri.parse("content://" + ProviderConfig.AUTHORITY +
+ return Uri.parse("content://" + LauncherProvider.AUTHORITY +
"/" + TABLE_NAME + "/" + id);
}
@@ -280,7 +278,7 @@ public class LauncherSettings {
public static final class Settings {
public static final Uri CONTENT_URI = Uri.parse("content://" +
- ProviderConfig.AUTHORITY + "/settings");
+ LauncherProvider.AUTHORITY + "/settings");
public static final String METHOD_CLEAR_EMPTY_DB_FLAG = "clear_empty_db_flag";
public static final String METHOD_WAS_EMPTY_DB_CREATED = "get_empty_db_flag";
@@ -291,7 +289,6 @@ public class LauncherSettings {
public static final String METHOD_NEW_SCREEN_ID = "generate_new_screen_id";
public static final String METHOD_CREATE_EMPTY_DB = "create_empty_db";
- public static final String METHOD_DELETE_DB = "delete_db";
public static final String METHOD_LOAD_DEFAULT_FAVORITES = "load_default_favorites";
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 39c466db8..f5af979ce 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -32,7 +32,7 @@ import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimationLayerSet;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.CircleRevealOutlineProvider;
+import com.android.launcher3.anim.CircleRevealOutlineProvider;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.widget.WidgetsContainerView;
diff --git a/src/com/android/launcher3/MainThreadExecutor.java b/src/com/android/launcher3/MainThreadExecutor.java
index 4ca0a59d8..509468233 100644
--- a/src/com/android/launcher3/MainThreadExecutor.java
+++ b/src/com/android/launcher3/MainThreadExecutor.java
@@ -18,14 +18,14 @@ package com.android.launcher3;
import android.os.Looper;
-import com.android.launcher3.util.LooperExecuter;
+import com.android.launcher3.util.LooperExecutor;
/**
* An executor service that executes its tasks on the main thread.
*
* Shutting down this executor is not supported.
*/
-public class MainThreadExecutor extends LooperExecuter {
+public class MainThreadExecutor extends LooperExecutor {
public MainThreadExecutor() {
super(Looper.getMainLooper());
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
new file mode 100644
index 000000000..e8bf0a5c3
--- /dev/null
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.android.launcher3.compat.LauncherAppsCompat;
+
+import java.util.List;
+
+/**
+ * BroadcastReceiver to handle session commit intent.
+ */
+public class SessionCommitReceiver extends BroadcastReceiver {
+
+ // Preference key for automatically adding icon to homescreen.
+ public static final String ADD_ICON_PREFERENCE_KEY = "pref_add_icon_to_home";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!isEnabled(context)) {
+ // User has decided to not add icons on homescreen.
+ return;
+ }
+
+ SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
+ UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
+ // TODO: Verify install reason
+ if (TextUtils.isEmpty(info.getAppPackageName())) {
+ return;
+ }
+
+ if (!Process.myUserHandle().equals(user)) {
+ // Managed profile is handled using ManagedProfileHeuristic
+ return;
+ }
+
+ List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(context)
+ .getActivityList(info.getAppPackageName(), user);
+ if (activities == null || activities.isEmpty()) {
+ // no activity found
+ return;
+ }
+ InstallShortcutReceiver.queueActivityInfo(activities.get(0), context);
+ }
+
+ public static boolean isEnabled(Context context) {
+ return Utilities.getPrefs(context).getBoolean(ADD_ICON_PREFERENCE_KEY, true);
+ }
+}
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index cedeb3967..552e24ae4 100644
--- a/src/com/android/launcher3/SettingsActivity.java
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -25,6 +25,7 @@ import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.provider.Settings;
import android.provider.Settings.System;
+import android.support.v4.os.BuildCompat;
/**
* Settings activity for Launcher. Currently implements the following setting: Allow rotation
@@ -72,6 +73,11 @@ public class SettingsActivity extends Activity {
mRotationLockObserver.onChange(true);
rotationPref.setDefaultValue(Utilities.getAllowRotationDefaultValue(getActivity()));
}
+
+ if (!BuildCompat.isAtLeastO()) {
+ getPreferenceScreen().removePreference(
+ findPreference(SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY));
+ }
}
@Override
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index b35dcb716..f0bb1c0c1 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -134,7 +134,6 @@ public class ShortcutInfo extends ItemInfoWithIcon {
title = info.title;
intent = new Intent(info.intent);
iconResource = info.iconResource;
- iconBitmap = info.iconBitmap;
status = info.status;
mInstallProgress = info.mInstallProgress;
isDisabled = info.isDisabled;
@@ -146,8 +145,6 @@ public class ShortcutInfo extends ItemInfoWithIcon {
title = Utilities.trim(info.title);
intent = new Intent(info.intent);
isDisabled = info.isDisabled;
- iconBitmap = info.iconBitmap;
- usingLowResIcon = info.usingLowResIcon;
}
/**
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index e68a5b030..0fac29f30 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
@@ -139,9 +140,10 @@ public class UninstallDropTarget extends ButtonDropTarget {
final Runnable checkIfUninstallWasSuccess = new Runnable() {
@Override
public void run() {
- String packageName = cn.getPackageName();
- boolean uninstallSuccessful = !AllAppsList.packageHasActivities(
- launcher, packageName, user);
+ // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
+ boolean uninstallSuccessful = LauncherAppsCompat.getInstance(launcher)
+ .getApplicationInfo(cn.getPackageName(),
+ PackageManager.MATCH_UNINSTALLED_PACKAGES, user) == null;
callback.onDragObjectRemoved(uninstallSuccessful);
}
};
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index abc53673b..2413d8ae6 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -51,7 +51,7 @@ import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
-import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.config.FeatureFlags;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
@@ -575,7 +575,7 @@ public final class Utilities {
try {
c.close();
} catch (IOException e) {
- if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD) {
Log.d(TAG, "Error closing", e);
}
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 6d52ea3da..8f8d32ccc 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -65,7 +65,6 @@ import com.android.launcher3.anim.AnimationLayerSet;
import com.android.launcher3.badge.FolderBadgeInfo;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
@@ -642,7 +641,8 @@ public class Workspace extends PagedView
// of workspace despite that it's not a true child.
// Note that it relies on the strict ordering of measuring the workspace before the QSB
// at the dragLayer level.
- if (getChildCount() > 0) {
+ // Only measure the QSB when the view is enabled
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN && getChildCount() > 0) {
CellLayout firstPage = (CellLayout) getChildAt(0);
int cellHeight = firstPage.getCellHeight();
@@ -2617,7 +2617,7 @@ public class Workspace extends PagedView
CellLayout parentCell = getParentCellLayoutForView(cell);
if (parentCell != null) {
parentCell.removeView(cell);
- } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
throw new NullPointerException("mDragInfo.cell has null parent");
}
addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
@@ -2950,7 +2950,7 @@ public class Workspace extends PagedView
ItemInfo item = d.dragInfo;
if (item == null) {
- if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD) {
throw new NullPointerException("DragObject has null info");
}
return;
@@ -3607,7 +3607,7 @@ public class Workspace extends PagedView
mDragInfo.container, mDragInfo.screenId);
if (cellLayout != null) {
cellLayout.onDropChild(mDragInfo.cell);
- } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
throw new RuntimeException("Invalid state: cellLayout == null in "
+ "Workspace#onDropCompleted. Please file a bug. ");
};
@@ -3633,7 +3633,7 @@ public class Workspace extends PagedView
CellLayout parentCell = getParentCellLayoutForView(v);
if (parentCell != null) {
parentCell.removeView(v);
- } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
// When an app is uninstalled using the drop target, we wait until resume to remove
// the icon. We also remove all the corresponding items from the workspace at
// {@link Launcher#bindComponentsRemoved}. That call can come before or after
@@ -3884,7 +3884,9 @@ public class Workspace extends PagedView
// The item may belong to a folder.
View parent = idToViewMap.get(itemToRemove.container);
if (parent != null) {
- ((FolderInfo) parent.getTag()).remove((ShortcutInfo) itemToRemove, false);
+ FolderInfo folderInfo = (FolderInfo) parent.getTag();
+ folderInfo.prepareAutoUpdate();
+ folderInfo.remove((ShortcutInfo) itemToRemove, false);
}
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 0732004d4..cc5fa8ce1 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -20,6 +20,8 @@ import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.InsetDrawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.text.Selection;
import android.text.Spannable;
@@ -46,6 +48,8 @@ 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.discovery.AppDiscoveryItem;
+import com.android.launcher3.discovery.AppDiscoveryUpdateState;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
@@ -211,7 +215,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
// IF scroller is at the very top OR there is no scroll bar because there is probably not
// enough items to scroll, THEN it's okay for the container to be pulled down.
- if (mAppsRecyclerView.getScrollBar().getThumbOffsetY() <= 0) {
+ if (mAppsRecyclerView.getCurrentScrollY() == 0) {
return true;
}
return false;
@@ -425,14 +429,22 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
@Override
public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
if (apps != null) {
- if (mApps.setOrderedFilter(apps)) {
- mAppsRecyclerView.onSearchResultsChanged();
- }
+ mApps.setOrderedFilter(apps);
+ mAppsRecyclerView.onSearchResultsChanged();
mAdapter.setLastSearchQuery(query);
}
}
@Override
+ public void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
+ @NonNull AppDiscoveryUpdateState state) {
+ if (!mLauncher.isDestroyed()) {
+ mApps.onAppDiscoverySearchUpdate(app, state);
+ mAppsRecyclerView.onSearchResultsChanged();
+ }
+ }
+
+ @Override
public void clearSearchResult() {
if (mApps.setOrderedFilter(null)) {
mAppsRecyclerView.onSearchResultsChanged();
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index 28b7685ed..a1ff8223a 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -19,6 +19,7 @@ import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.android.launcher3.BaseRecyclerViewFastScrollBar;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.util.Thunk;
@@ -45,8 +46,7 @@ public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallb
// Set of all views animated during fast scroll. We keep track of these ourselves since there
// is no way to reset a view once it gets scrapped or recycled without other hacks
- private HashSet<BaseRecyclerViewFastScrollBar.FastScrollFocusableView> mTrackedFastScrollViews =
- new HashSet<>();
+ private HashSet<RecyclerView.ViewHolder> mTrackedFastScrollViews = new HashSet<>();
// Smooth fast-scroll animation frames
@Thunk int mFastScrollFrameIndex;
@@ -186,12 +186,7 @@ public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallb
public void onBindView(AllAppsGridAdapter.ViewHolder holder) {
// Update newly bound views to the current fast scroll state if we are fast scrolling
if (mCurrentFastScrollSection != null || mTargetFastScrollSection != null) {
- if (holder.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
- BaseRecyclerViewFastScrollBar.FastScrollFocusableView v =
- (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) holder.itemView;
- updateViewFastScrollFocusState(v, holder.getPosition(), false /* animated */);
- mTrackedFastScrollViews.add(v);
- }
+ mTrackedFastScrollViews.add(holder);
}
}
@@ -201,9 +196,9 @@ public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallb
private void trackAllChildViews() {
int childCount = mRv.getChildCount();
for (int i = 0; i < childCount; i++) {
- View v = mRv.getChildAt(i);
- if (v instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
- mTrackedFastScrollViews.add((BaseRecyclerViewFastScrollBar.FastScrollFocusableView) v);
+ RecyclerView.ViewHolder viewHolder = mRv.getChildViewHolder(mRv.getChildAt(i));
+ if (viewHolder != null) {
+ mTrackedFastScrollViews.add(viewHolder);
}
}
}
@@ -212,27 +207,16 @@ public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallb
* Updates the fast scroll focus on all the children.
*/
private void updateTrackedViewsFastScrollFocusState() {
- for (BaseRecyclerViewFastScrollBar.FastScrollFocusableView v : mTrackedFastScrollViews) {
- RecyclerView.ViewHolder viewHolder = mRv.getChildViewHolder((View) v);
- int pos = (viewHolder != null) ? viewHolder.getPosition() : -1;
- updateViewFastScrollFocusState(v, pos, true);
- }
- }
-
- /**
- * Updates the fast scroll focus on all a given view.
- */
- private void updateViewFastScrollFocusState(BaseRecyclerViewFastScrollBar.FastScrollFocusableView v,
- int pos, boolean animated) {
- FastBitmapDrawable.State newState = FastBitmapDrawable.State.NORMAL;
- if (mCurrentFastScrollSection != null && pos > -1) {
- AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos);
- boolean highlight = item.sectionName.equals(mCurrentFastScrollSection) &&
- item.position == mTargetFastScrollPosition;
- newState = highlight ?
- FastBitmapDrawable.State.FAST_SCROLL_HIGHLIGHTED :
- FastBitmapDrawable.State.FAST_SCROLL_UNHIGHLIGHTED;
+ for (RecyclerView.ViewHolder viewHolder : mTrackedFastScrollViews) {
+ int pos = viewHolder.getAdapterPosition();
+ boolean isActive = false;
+ if (mCurrentFastScrollSection != null && pos > -1) {
+ AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos);
+ isActive = item != null &&
+ mCurrentFastScrollSection.equals(item.sectionName) &&
+ item.position == mTargetFastScrollPosition;
+ }
+ viewHolder.itemView.setActivated(isActive);
}
- v.setFastScrollFocusState(newState, animated);
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index bd877f248..59cac8d26 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -19,8 +19,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Point;
-import android.graphics.Rect;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -33,11 +33,15 @@ import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
+import com.android.launcher3.discovery.AppDiscoveryAppInfo;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
+import com.android.launcher3.discovery.AppDiscoveryItemView;
+
+import java.util.List;
/**
* The grid view adapter of all the apps.
@@ -64,6 +68,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
public static final int VIEW_TYPE_SEARCH_DIVIDER = 1 << 6;
// The divider that separates prediction icons from the app list
public static final int VIEW_TYPE_PREDICTION_DIVIDER = 1 << 7;
+ public static final int VIEW_TYPE_APPS_LOADING_DIVIDER = 1 << 8;
+ public static final int VIEW_TYPE_DISCOVERY_ITEM = 1 << 9;
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_SEARCH_DIVIDER
@@ -71,6 +77,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
| VIEW_TYPE_PREDICTION_DIVIDER;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON
| VIEW_TYPE_PREDICTION_ICON;
+ public static final int VIEW_TYPE_MASK_CONTENT = VIEW_TYPE_MASK_ICON
+ | VIEW_TYPE_DISCOVERY_ITEM;
public interface BindViewCallback {
@@ -81,7 +89,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
* ViewHolder for each icon.
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
-
public ViewHolder(View v) {
super(v);
}
@@ -105,17 +112,53 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
final AccessibilityRecordCompat record = AccessibilityEventCompat
.asRecord(event);
record.setItemCount(mApps.getNumFilteredApps());
+ record.setFromIndex(Math.max(0,
+ record.getFromIndex() - getRowsNotForAccessibility(record.getFromIndex())));
+ record.setToIndex(Math.max(0,
+ record.getToIndex() - getRowsNotForAccessibility(record.getToIndex())));
}
@Override
public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
RecyclerView.State state) {
- if (mApps.hasNoFilteredResults()) {
- // Disregard the no-search-results text as a list item for accessibility
- return 0;
- } else {
- return super.getRowCountForAccessibility(recycler, state);
+ return super.getRowCountForAccessibility(recycler, state) -
+ getRowsNotForAccessibility(mApps.getAdapterItems().size() - 1);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
+ RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info);
+
+ ViewGroup.LayoutParams lp = host.getLayoutParams();
+ AccessibilityNodeInfoCompat.CollectionItemInfoCompat cic = info.getCollectionItemInfo();
+ if (!(lp instanceof LayoutParams) || (cic == null)) {
+ return;
}
+ LayoutParams glp = (LayoutParams) lp;
+ info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
+ cic.getRowIndex() - getRowsNotForAccessibility(glp.getViewAdapterPosition()),
+ cic.getRowSpan(),
+ cic.getColumnIndex(),
+ cic.getColumnSpan(),
+ cic.isHeading(),
+ cic.isSelected()));
+ }
+
+ /**
+ * Returns the number of rows before {@param adapterPosition}, including this position
+ * which should not be counted towards the collection info.
+ */
+ private int getRowsNotForAccessibility(int adapterPosition) {
+ List<AdapterItem> items = mApps.getAdapterItems();
+ adapterPosition = Math.max(adapterPosition, mApps.getAdapterItems().size() - 1);
+ int extraRows = 0;
+ for (int i = 0; i <= adapterPosition; i++) {
+ if (!isViewType(items.get(i).viewType, VIEW_TYPE_MASK_CONTENT)) {
+ extraRows++;
+ }
+ }
+ return extraRows;
}
@Override
@@ -234,8 +277,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_ICON:
- /* falls through */
- case VIEW_TYPE_PREDICTION_ICON: {
+ case VIEW_TYPE_PREDICTION_ICON:
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
R.layout.all_apps_icon, parent, false);
icon.setOnClickListener(mIconClickListener);
@@ -245,14 +287,14 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
icon.setOnFocusChangeListener(mIconFocusListener);
// Ensure the all apps icon height matches the workspace icons
- DeviceProfile profile = mLauncher.getDeviceProfile();
- Point cellSize = profile.getCellSize();
- GridLayoutManager.LayoutParams lp =
- (GridLayoutManager.LayoutParams) icon.getLayoutParams();
- lp.height = cellSize.y;
- icon.setLayoutParams(lp);
+ icon.getLayoutParams().height = getCellSize().y;
return new ViewHolder(icon);
- }
+ case VIEW_TYPE_DISCOVERY_ITEM:
+ AppDiscoveryItemView appDiscoveryItemView = (AppDiscoveryItemView) mLayoutInflater
+ .inflate(R.layout.all_apps_discovery_item, parent, false);
+ appDiscoveryItemView.init(mIconClickListener, mLauncher.getAccessibilityDelegate(),
+ mIconLongClickListener);
+ return new ViewHolder(appDiscoveryItemView);
case VIEW_TYPE_EMPTY_SEARCH:
return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
parent, false));
@@ -269,8 +311,11 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
case VIEW_TYPE_SEARCH_DIVIDER:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.all_apps_search_divider, parent, false));
+ case VIEW_TYPE_APPS_LOADING_DIVIDER:
+ View loadingDividerView = mLayoutInflater.inflate(
+ R.layout.all_apps_discovery_loading_divider, parent, false);
+ return new ViewHolder(loadingDividerView);
case VIEW_TYPE_PREDICTION_DIVIDER:
- /* falls through */
case VIEW_TYPE_SEARCH_MARKET_DIVIDER:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.all_apps_divider, parent, false));
@@ -279,23 +324,26 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
}
}
+ private Point getCellSize() {
+ return mLauncher.getDeviceProfile().getCellSize();
+ }
+
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
- case VIEW_TYPE_ICON: {
+ case VIEW_TYPE_ICON:
+ case VIEW_TYPE_PREDICTION_ICON:
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
BubbleTextView icon = (BubbleTextView) holder.itemView;
icon.applyFromApplicationInfo(info);
icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
break;
- }
- case VIEW_TYPE_PREDICTION_ICON: {
- AppInfo info = mApps.getAdapterItems().get(position).appInfo;
- BubbleTextView icon = (BubbleTextView) holder.itemView;
- icon.applyFromApplicationInfo(info);
- icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
+ case VIEW_TYPE_DISCOVERY_ITEM:
+ AppDiscoveryAppInfo appDiscoveryAppInfo = (AppDiscoveryAppInfo)
+ mApps.getAdapterItems().get(position).appInfo;
+ AppDiscoveryItemView view = (AppDiscoveryItemView) holder.itemView;
+ view.apply(appDiscoveryAppInfo);
break;
- }
case VIEW_TYPE_EMPTY_SEARCH:
TextView emptyViewText = (TextView) holder.itemView;
emptyViewText.setText(mEmptySearchMessage);
@@ -310,6 +358,15 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
searchView.setVisibility(View.GONE);
}
break;
+ case VIEW_TYPE_APPS_LOADING_DIVIDER:
+ int visLoading = mApps.isAppDiscoveryRunning() ? View.VISIBLE : View.GONE;
+ int visLoaded = !mApps.isAppDiscoveryRunning() ? View.VISIBLE : View.GONE;
+ holder.itemView.findViewById(R.id.loadingProgressBar).setVisibility(visLoading);
+ holder.itemView.findViewById(R.id.loadedDivider).setVisibility(visLoaded);
+ break;
+ case VIEW_TYPE_SEARCH_MARKET_DIVIDER:
+ // nothing to do
+ break;
}
if (mBindViewCallback != null) {
mBindViewCallback.onBindView(holder);
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index a41d83244..64e2fcb3d 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -30,6 +30,7 @@ import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -110,44 +111,40 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
* all the different view types.
*/
public void preMeasureViews(AllAppsGridAdapter adapter) {
+ View icon = adapter.onCreateViewHolder(this, AllAppsGridAdapter.VIEW_TYPE_ICON).itemView;
+ final int iconHeight = icon.getLayoutParams().height;
+ mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, iconHeight);
+ mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, iconHeight);
+
final int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(
getResources().getDisplayMetrics().widthPixels, View.MeasureSpec.AT_MOST);
final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
getResources().getDisplayMetrics().heightPixels, View.MeasureSpec.AT_MOST);
- // Icons
- BubbleTextView icon = (BubbleTextView) adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_ICON).itemView;
- int iconHeight = icon.getLayoutParams().height;
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, iconHeight);
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, iconHeight);
+ putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+ AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER,
+ AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER);
+ putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+ AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER);
+ putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+ AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET);
+ putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+ AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH);
+
+ if (FeatureFlags.DISCOVERY_ENABLED) {
+ putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+ AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER);
+ putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+ AllAppsGridAdapter.VIEW_TYPE_DISCOVERY_ITEM);
+ }
+ }
- // Search divider
- View searchDivider = adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER).itemView;
- searchDivider.measure(widthMeasureSpec, heightMeasureSpec);
- int searchDividerHeight = searchDivider.getMeasuredHeight();
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER, searchDividerHeight);
-
- // Generic dividers
- View divider = adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER).itemView;
- divider.measure(widthMeasureSpec, heightMeasureSpec);
- int dividerHeight = divider.getMeasuredHeight();
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER, dividerHeight);
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER, dividerHeight);
-
- // Search views
- View emptySearch = adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH).itemView;
- emptySearch.measure(widthMeasureSpec, heightMeasureSpec);
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH,
- emptySearch.getMeasuredHeight());
- View searchMarket = adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET).itemView;
- searchMarket.measure(widthMeasureSpec, heightMeasureSpec);
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET,
- searchMarket.getMeasuredHeight());
+ private void putSameHeightFor(AllAppsGridAdapter adapter, int w, int h, int... viewTypes) {
+ View view = adapter.onCreateViewHolder(this, viewTypes[0]).itemView;
+ view.measure(w, h);
+ for (int viewType : viewTypes) {
+ mViewHeights.put(viewType, view.getMeasuredHeight());
+ }
}
/**
@@ -207,7 +204,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
// Always scroll the view to the top so the user can see the changed results
scrollToTop();
- if (mApps.hasNoFilteredResults()) {
+ if (mApps.shouldShowEmptySearch()) {
if (mEmptySearchBackground == null) {
mEmptySearchBackground = DrawableFactory.get(getContext())
.getAllAppsBackground(getContext());
@@ -438,4 +435,5 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
x + mEmptySearchBackground.getIntrinsicWidth(),
y + mEmptySearchBackground.getIntrinsicHeight());
}
+
}
diff --git a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
index 365ab3185..c7ba3abc6 100644
--- a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
@@ -19,6 +19,8 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -32,6 +34,8 @@ import android.widget.TextView.OnEditorActionListener;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
+import com.android.launcher3.discovery.AppDiscoveryItem;
+import com.android.launcher3.discovery.AppDiscoveryUpdateState;
import com.android.launcher3.util.ComponentKey;
import java.util.ArrayList;
@@ -46,7 +50,7 @@ public abstract class AllAppsSearchBarController
protected AlphabeticalAppsList mApps;
protected Callbacks mCb;
protected ExtendedEditText mInput;
- private String mQuery;
+ protected String mQuery;
protected DefaultAppSearchAlgorithm mSearchAlgorithm;
protected InputMethodManager mInputMethodManager;
@@ -73,6 +77,14 @@ public abstract class AllAppsSearchBarController
mInput.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
mSearchAlgorithm = onInitializeSearch();
+
+ onInitialized();
+ }
+
+ /**
+ * You can override this method to perform custom initialization.
+ */
+ protected void onInitialized() {
}
/**
@@ -117,6 +129,7 @@ public abstract class AllAppsSearchBarController
if (actionId != EditorInfo.IME_ACTION_SEARCH) {
return false;
}
+
// Skip if the query is empty
String query = v.getText().toString();
if (query.isEmpty()) {
@@ -206,5 +219,19 @@ public abstract class AllAppsSearchBarController
* Called when the search results should be cleared.
*/
void clearSearchResult();
+
+
+ /**
+ * Called when the app discovery is providing an update of search, which can either be
+ * START for starting a new discovery,
+ * UPDATE for providing a new search result, can be called multiple times,
+ * END for indicating the end of results.
+ *
+ * @param app result item if UPDATE, else null
+ * @param app the update state, START, UPDATE or END
+ */
+ void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
+ @NonNull AppDiscoveryUpdateState state);
}
+
} \ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 0bfbd3eba..9e32d257d 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -17,12 +17,17 @@ package com.android.launcher3.allapps;
import android.content.Context;
import android.os.Process;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.util.Log;
import com.android.launcher3.AppInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.compat.AlphabeticIndexCompat;
-import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.discovery.AppDiscoveryAppInfo;
+import com.android.launcher3.discovery.AppDiscoveryItem;
+import com.android.launcher3.discovery.AppDiscoveryUpdateState;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.LabelComparator;
@@ -48,6 +53,8 @@ public class AlphabeticalAppsList {
private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
+ private AppDiscoveryUpdateState mAppDiscoveryUpdateState;
+
/**
* Info about a fast scroller section, depending if sections are merged, the fast scroller
* sections will not be the same set as the section headers.
@@ -106,6 +113,17 @@ public class AlphabeticalAppsList {
return item;
}
+ public static AdapterItem asDiscoveryItem(int pos, String sectionName, AppInfo appInfo,
+ int appIndex) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = AllAppsGridAdapter.VIEW_TYPE_DISCOVERY_ITEM;
+ item.position = pos;
+ item.sectionName = sectionName;
+ item.appInfo = appInfo;
+ item.appIndex = appIndex;
+ return item;
+ }
+
public static AdapterItem asEmptySearch(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH;
@@ -134,6 +152,13 @@ public class AlphabeticalAppsList {
return item;
}
+ public static AdapterItem asLoadingDivider(int pos) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER;
+ item.position = pos;
+ return item;
+ }
+
public static AdapterItem asMarketSearch(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET;
@@ -142,22 +167,24 @@ public class AlphabeticalAppsList {
}
}
- private Launcher mLauncher;
+ private final Launcher mLauncher;
// The set of apps from the system not including predictions
private final List<AppInfo> mApps = new ArrayList<>();
private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
// The set of filtered apps with the current filter
- private List<AppInfo> mFilteredApps = new ArrayList<>();
+ private final List<AppInfo> mFilteredApps = new ArrayList<>();
// The current set of adapter items
- private List<AdapterItem> mAdapterItems = new ArrayList<>();
+ private final ArrayList<AdapterItem> mAdapterItems = new ArrayList<>();
// The set of sections that we allow fast-scrolling to (includes non-merged sections)
- private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
+ private final List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
// The set of predicted app component names
- private List<ComponentKey> mPredictedAppComponents = new ArrayList<>();
+ private final List<ComponentKey> mPredictedAppComponents = new ArrayList<>();
// The set of predicted apps resolved from the component names and the current set of apps
- private List<AppInfo> mPredictedApps = new ArrayList<>();
+ private final List<AppInfo> mPredictedApps = new ArrayList<>();
+ private final List<AppDiscoveryAppInfo> mDiscoveredApps = new ArrayList<>();
+
// The of ordered component names as a result of a search query
private ArrayList<ComponentKey> mSearchResults;
private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
@@ -240,6 +267,10 @@ public class AlphabeticalAppsList {
return (mSearchResults != null) && mFilteredApps.isEmpty();
}
+ boolean shouldShowEmptySearch() {
+ return hasNoFilteredResults() && !isAppDiscoveryRunning() && mDiscoveredApps.isEmpty();
+ }
+
/**
* Sets the sorted list of filtered components.
*/
@@ -253,6 +284,20 @@ public class AlphabeticalAppsList {
return false;
}
+ public void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
+ @NonNull AppDiscoveryUpdateState state) {
+ mAppDiscoveryUpdateState = state;
+ switch (state) {
+ case START:
+ mDiscoveredApps.clear();
+ break;
+ case UPDATE:
+ mDiscoveredApps.add(new AppDiscoveryAppInfo(app, mLauncher));
+ break;
+ }
+ updateAdapterItems();
+ }
+
/**
* Sets the current set of predicted apps. Since this can be called before we get the full set
* of applications, we should merge the results only in onAppsUpdated() which is idempotent.
@@ -350,6 +395,17 @@ public class AlphabeticalAppsList {
* mCachedSectionNames to have been calculated for the set of all apps in mApps.
*/
private void updateAdapterItems() {
+ refillAdapterItems();
+ refreshRecyclerView();
+ }
+
+ private void refreshRecyclerView() {
+ if (mAdapter != null) {
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ private void refillAdapterItems() {
String lastSectionName = null;
FastScrollSectionInfo lastFastScrollerSectionInfo = null;
int position = 0;
@@ -384,7 +440,7 @@ public class AlphabeticalAppsList {
if (info != null) {
mPredictedApps.add(info);
} else {
- if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD) {
Log.e(TAG, "Predicted app not found: " + ck);
}
}
@@ -435,14 +491,30 @@ public class AlphabeticalAppsList {
mFilteredApps.add(info);
}
- // Append the search market item if we are currently searching
if (hasFilter()) {
- if (hasNoFilteredResults()) {
- mAdapterItems.add(AdapterItem.asEmptySearch(position++));
+ if (isAppDiscoveryRunning() || mDiscoveredApps.size() > 0) {
+ mAdapterItems.add(AdapterItem.asLoadingDivider(position++));
+
+ // Append all app discovery results
+ for (int i = 0; i < mDiscoveredApps.size(); i++) {
+ AppDiscoveryAppInfo appDiscoveryAppInfo = mDiscoveredApps.get(i);
+ AdapterItem item = AdapterItem.asDiscoveryItem(position++,
+ "", appDiscoveryAppInfo, appIndex++);
+ mAdapterItems.add(item);
+ }
+
+ if (!isAppDiscoveryRunning()) {
+ mAdapterItems.add(AdapterItem.asMarketSearch(position++));
+ }
} else {
- mAdapterItems.add(AdapterItem.asMarketDivider(position++));
+ // Append the search market item
+ if (hasNoFilteredResults()) {
+ mAdapterItems.add(AdapterItem.asEmptySearch(position++));
+ } else {
+ mAdapterItems.add(AdapterItem.asMarketDivider(position++));
+ }
+ mAdapterItems.add(AdapterItem.asMarketSearch(position++));
}
- mAdapterItems.add(AdapterItem.asMarketSearch(position++));
}
if (mNumAppsPerRow != 0) {
@@ -498,11 +570,11 @@ public class AlphabeticalAppsList {
break;
}
}
+ }
- // Refresh the recycler view
- if (mAdapter != null) {
- mAdapter.notifyDataSetChanged();
- }
+ public boolean isAppDiscoveryRunning() {
+ return mAppDiscoveryUpdateState == AppDiscoveryUpdateState.START
+ || mAppDiscoveryUpdateState == AppDiscoveryUpdateState.UPDATE;
}
private List<AppInfo> getFiltersAppInfos() {
@@ -532,4 +604,5 @@ public class AlphabeticalAppsList {
}
return sectionName;
}
+
}
diff --git a/src/com/android/launcher3/util/CircleRevealOutlineProvider.java b/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java
index 9fe51476d..9fb6b498b 100644
--- a/src/com/android/launcher3/util/CircleRevealOutlineProvider.java
+++ b/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3.util;
+package com.android.launcher3.anim;
public class CircleRevealOutlineProvider extends RevealOutlineAnimation {
diff --git a/src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java b/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java
index 89dda3b26..679e8e32f 100644
--- a/src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java
+++ b/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,28 +14,28 @@
* limitations under the License.
*/
-package com.android.launcher3.util;
+package com.android.launcher3.anim;
import android.graphics.Rect;
/**
- * Extension of {@link PillRevealOutlineProvider} which only changes the width of the pill.
+ * Extension of {@link PillRevealOutlineProvider} which only changes the height of the pill.
+ * For now, we assume the height is added/removed from the bottom.
*/
-public class PillWidthRevealOutlineProvider extends PillRevealOutlineProvider {
+public class PillHeightRevealOutlineProvider extends PillRevealOutlineProvider {
- private final int mStartLeft;
- private final int mStartRight;
+ private final int mNewHeight;
- public PillWidthRevealOutlineProvider(Rect pillRect, int left, int right) {
- super(0, 0, pillRect);
+ public PillHeightRevealOutlineProvider(Rect pillRect, float radius, int newHeight) {
+ super(0, 0, pillRect, radius);
mOutline.set(pillRect);
- mStartLeft = left;
- mStartRight = right;
+ mNewHeight = newHeight;
}
@Override
public void setProgress(float progress) {
- mOutline.left = (int) (progress * mPillRect.left + (1 - progress) * mStartLeft);
- mOutline.right = (int) (progress * mPillRect.right + (1 - progress) * mStartRight);
+ mOutline.top = 0;
+ int heightDifference = mPillRect.height() - mNewHeight;
+ mOutline.bottom = (int) (mPillRect.bottom - heightDifference * (1 - progress));
}
}
diff --git a/src/com/android/launcher3/util/PillRevealOutlineProvider.java b/src/com/android/launcher3/anim/PillRevealOutlineProvider.java
index a57d69fab..450f9db9a 100644
--- a/src/com/android/launcher3/util/PillRevealOutlineProvider.java
+++ b/src/com/android/launcher3/anim/PillRevealOutlineProvider.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3.util;
+package com.android.launcher3.anim;
import android.graphics.Rect;
import android.view.ViewOutlineProvider;
diff --git a/src/com/android/launcher3/anim/PropertyResetListener.java b/src/com/android/launcher3/anim/PropertyResetListener.java
new file mode 100644
index 000000000..eefb0148c
--- /dev/null
+++ b/src/com/android/launcher3/anim/PropertyResetListener.java
@@ -0,0 +1,41 @@
+/*
+ * 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.anim;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.Property;
+
+/**
+ * An AnimatorListener that sets the given property to the given value at the end of the animation.
+ */
+public class PropertyResetListener<T, V> extends AnimatorListenerAdapter {
+
+ private Property<T, V> mPropertyToReset;
+ private V mResetToValue;
+
+ public PropertyResetListener(Property<T, V> propertyToReset, V resetToValue) {
+ mPropertyToReset = propertyToReset;
+ mResetToValue = resetToValue;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPropertyToReset.set((T) ((ObjectAnimator) animation).getTarget(), mResetToValue);
+ }
+}
diff --git a/src/com/android/launcher3/util/RevealOutlineAnimation.java b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
index 456047775..51d00d947 100644
--- a/src/com/android/launcher3/util/RevealOutlineAnimation.java
+++ b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
@@ -1,4 +1,4 @@
-package com.android.launcher3.util;
+package com.android.launcher3.anim;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -83,4 +83,8 @@ public abstract class RevealOutlineAnimation extends ViewOutlineProvider {
public void getOutline(View v, Outline outline) {
outline.setRoundRect(mOutline, mOutlineRadius);
}
+
+ public float getRadius() {
+ return mOutlineRadius;
+ }
}
diff --git a/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java b/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java
new file mode 100644
index 000000000..9c09477bb
--- /dev/null
+++ b/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java
@@ -0,0 +1,57 @@
+/*
+ * 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.anim;
+
+import android.graphics.Rect;
+
+/**
+ * A {@link RevealOutlineAnimation} that provides an outline that interpolates between two radii
+ * and two {@link Rect}s.
+ *
+ * An example usage of this provider is an outline that starts out as a circle and ends
+ * as a rounded rectangle.
+ */
+public class RoundedRectRevealOutlineProvider extends RevealOutlineAnimation {
+ private final float mStartRadius;
+ private final float mEndRadius;
+
+ private final Rect mStartRect;
+ private final Rect mEndRect;
+
+ public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
+ Rect endRect) {
+ mStartRadius = startRadius;
+ mEndRadius = endRadius;
+ mStartRect = startRect;
+ mEndRect = endRect;
+ }
+
+ @Override
+ public boolean shouldRemoveElevationDuringAnimation() {
+ return false;
+ }
+
+ @Override
+ public void setProgress(float progress) {
+ mOutlineRadius = (1 - progress) * mStartRadius + progress * mEndRadius;
+
+ mOutline.left = (int) ((1 - progress) * mStartRect.left + progress * mEndRect.left);
+ mOutline.top = (int) ((1 - progress) * mStartRect.top + progress * mEndRect.top);
+ mOutline.right = (int) ((1 - progress) * mStartRect.right + progress * mEndRect.right);
+ mOutline.bottom = (int) ((1 - progress) * mStartRect.bottom + progress * mEndRect.bottom);
+ }
+}
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
index 8bbc2afa2..58969289e 100644
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -20,14 +20,15 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.graphics.Shader;
import android.support.annotation.Nullable;
import com.android.launcher3.R;
import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.graphics.ShadowGenerator;
/**
* Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
@@ -40,10 +41,12 @@ public class BadgeRenderer {
private final int mTextHeight;
private final IconDrawer mLargeIconDrawer;
private final IconDrawer mSmallIconDrawer;
- private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG
+ | Paint.FILTER_BITMAP_FLAG);
+ private final Bitmap mBackgroundWithShadow;
- public BadgeRenderer(Context context) {
+ public BadgeRenderer(final Context context) {
mContext = context;
Resources res = context.getResources();
mSize = res.getDimensionPixelSize(R.dimen.badge_size);
@@ -51,10 +54,13 @@ public class BadgeRenderer {
mSmallIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_large_padding));
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTextSize(res.getDimensionPixelSize(R.dimen.badge_text_size));
+ mTextPaint.setFakeBoldText(true);
// Measure the text height.
Rect tempTextHeight = new Rect();
mTextPaint.getTextBounds("0", 0, 1, tempTextHeight);
mTextHeight = tempTextHeight.height();
+
+ mBackgroundWithShadow = ShadowGenerator.createCircleWithShadow(Color.WHITE, mSize);
}
/**
@@ -67,13 +73,15 @@ public class BadgeRenderer {
*/
public void draw(Canvas canvas, IconPalette palette, @Nullable BadgeInfo badgeInfo,
Rect iconBounds, float badgeScale) {
- mBackgroundPaint.setColor(palette.backgroundColor);
mTextPaint.setColor(palette.textColor);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
// We draw the badge relative to its center.
canvas.translate(iconBounds.right - mSize / 2, iconBounds.top + mSize / 2);
canvas.scale(badgeScale, badgeScale);
- canvas.drawCircle(0, 0, mSize / 2, mBackgroundPaint);
+ mBackgroundPaint.setColorFilter(palette.backgroundColorMatrixFilter);
+ int backgroundSize = mBackgroundWithShadow.getHeight(); // Same as width.
+ canvas.drawBitmap(mBackgroundWithShadow, -backgroundSize / 2, -backgroundSize / 2,
+ mBackgroundPaint);
IconDrawer iconDrawer = badgeInfo != null && badgeInfo.isIconLarge()
? mLargeIconDrawer : mSmallIconDrawer;
Shader icon = badgeInfo == null ? null : badgeInfo.getNotificationIconForBadge(
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index b9142ed16..2eb5b023b 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -19,6 +19,7 @@ package com.android.launcher3.compat;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.graphics.Rect;
import android.os.Bundle;
@@ -72,6 +73,8 @@ public abstract class LauncherAppsCompat {
UserHandle user);
public abstract void startActivityForProfile(ComponentName component, UserHandle user,
Rect sourceBounds, Bundle opts);
+ public abstract ApplicationInfo getApplicationInfo(
+ String packageName, int flags, UserHandle user);
public abstract void showAppDetailsForProfile(ComponentName component, UserHandle user);
public abstract void addOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
public abstract void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
index 3cb721ccc..459017392 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
@@ -19,6 +19,7 @@ package com.android.launcher3.compat;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
@@ -26,6 +27,7 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Process;
import android.os.UserHandle;
import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVL;
@@ -65,6 +67,32 @@ public class LauncherAppsCompatVL extends LauncherAppsCompat {
}
@Override
+ public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) {
+ final boolean isPrimaryUser = Process.myUserHandle().equals(user);
+ if (!isPrimaryUser && (flags == 0)) {
+ // We are looking for an installed app on a secondary profile. Prior to O, the only
+ // entry point for work profiles is through the LauncherActivity.
+ List<LauncherActivityInfo> activityList =
+ mLauncherApps.getActivityList(packageName, user);
+ return activityList.size() > 0 ? activityList.get(0).getApplicationInfo() : null;
+ }
+ try {
+ ApplicationInfo info =
+ mContext.getPackageManager().getApplicationInfo(packageName, flags);
+ // There is no way to check if the app is installed for managed profile. But for
+ // primary profile, we can still have this check.
+ if (isPrimaryUser && ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0)
+ || !info.enabled) {
+ return null;
+ }
+ return info;
+ } catch (PackageManager.NameNotFoundException e) {
+ // Package not found
+ return null;
+ }
+ }
+
+ @Override
public void showAppDetailsForProfile(ComponentName component, UserHandle user) {
mLauncherApps.startAppDetailsActivity(component, user, null, null);
}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
index 0610726a7..27433796a 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -17,6 +17,7 @@
package com.android.launcher3.compat;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.os.UserHandle;
@@ -34,6 +35,13 @@ public class LauncherAppsCompatVO extends LauncherAppsCompatVL {
}
@Override
+ public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) {
+ ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
+ return info == null || (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
+ ? null : info;
+ }
+
+ @Override
public List<ShortcutConfigActivityInfo> getCustomShortcutActivityList() {
List<ShortcutConfigActivityInfo> result = new ArrayList<>();
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
new file mode 100644
index 000000000..50e979aac
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
@@ -0,0 +1,88 @@
+/*
+ * 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.discovery;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+
+public class AppDiscoveryAppInfo extends AppInfo {
+
+ private final @NonNull Launcher mLauncher;
+
+ public final boolean showAsDiscoveryItem;
+ public final boolean isInstantApp;
+ public final float rating;
+ public final long reviewCount;
+ public final @NonNull String publisher;
+ public final @NonNull Intent installIntent;
+ public final @NonNull Intent launchIntent;
+ public final @Nullable String priceFormatted;
+
+ public AppDiscoveryAppInfo(AppDiscoveryItem item, Launcher launcher) {
+ this.mLauncher = launcher;
+ this.intent = item.isInstantApp ? item.launchIntent : item.installIntent;
+ this.title = item.title;
+ this.iconBitmap = item.bitmap;
+ this.isDisabled = ShortcutInfo.DEFAULT;
+ this.usingLowResIcon = false;
+ this.isInstantApp = item.isInstantApp;
+ this.rating = item.starRating;
+ this.showAsDiscoveryItem = true;
+ this.publisher = item.publisher != null ? item.publisher : "";
+ this.priceFormatted = item.price;
+ this.componentName = new ComponentName(item.packageName, "");
+ this.installIntent = item.installIntent;
+ this.launchIntent = item.launchIntent;
+ this.reviewCount = item.reviewCount;
+ this.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ }
+
+ @Override
+ public ShortcutInfo makeShortcut() {
+ if (!isDragAndDropSupported()) {
+ throw new RuntimeException("DnD is currently not supported for discovered store apps");
+ }
+ ShortcutInfo shortcutInfo = super.makeShortcut();
+ if (isInstantApp) {
+ int iconSize = iconBitmap.getWidth();
+ int badgeSize = mLauncher.getResources().getDimensionPixelOffset(R.dimen.badge_size);
+ Bitmap icon = Bitmap.createBitmap(iconBitmap);
+ Drawable badgeDrawable = mLauncher.getDrawable(R.drawable.ic_instant_app);
+ badgeDrawable.setBounds(iconSize - badgeSize, iconSize - badgeSize, iconSize, iconSize);
+ Canvas canvas = new Canvas(icon);
+ badgeDrawable.draw(canvas);
+ shortcutInfo.iconBitmap = icon;
+ }
+ return shortcutInfo;
+ }
+
+ public boolean isDragAndDropSupported() {
+ return isInstantApp;
+ }
+
+}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryItem.java b/src/com/android/launcher3/discovery/AppDiscoveryItem.java
new file mode 100644
index 000000000..7c10371d0
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryItem.java
@@ -0,0 +1,62 @@
+/*
+ * 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.discovery;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+
+/**
+ * This class represents the model for a discovered app via app discovery.
+ * It holds all information for one result retrieved from an app discovery service.
+ */
+public class AppDiscoveryItem {
+
+ public final String packageName;
+ public final boolean isInstantApp;
+ public final float starRating;
+ public final long reviewCount;
+ public final Intent launchIntent;
+ public final Intent installIntent;
+ public final CharSequence title;
+ public final String publisher;
+ public final String price;
+ public final Bitmap bitmap;
+
+ public AppDiscoveryItem(String packageName,
+ boolean isInstantApp,
+ float starRating,
+ long reviewCount,
+ CharSequence title,
+ String publisher,
+ Bitmap bitmap,
+ String price,
+ Intent launchIntent,
+ Intent installIntent) {
+ this.packageName = packageName;
+ this.isInstantApp = isInstantApp;
+ this.starRating = starRating;
+ this.reviewCount = reviewCount;
+ this.launchIntent = launchIntent;
+ this.installIntent = installIntent;
+ this.title = title;
+ this.publisher = publisher;
+ this.price = price;
+ this.bitmap = bitmap;
+ }
+
+}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryItemView.java b/src/com/android/launcher3/discovery/AppDiscoveryItemView.java
new file mode 100644
index 000000000..6faad87ab
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryItemView.java
@@ -0,0 +1,100 @@
+/*
+ * 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.discovery;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+public class AppDiscoveryItemView extends RelativeLayout {
+
+ private static boolean SHOW_REVIEW_COUNT = false;
+
+ private ImageView mImage;
+ private ImageView mBadge;
+ private TextView mTitle;
+ private TextView mRatingText;
+ private RatingView mRatingView;
+ private TextView mReviewCount;
+ private TextView mPrice;
+ private OnLongClickListener mOnLongClickListener;
+
+ public AppDiscoveryItemView(Context context) {
+ this(context, null);
+ }
+
+ public AppDiscoveryItemView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AppDiscoveryItemView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ this.mImage = (ImageView) findViewById(R.id.image);
+ this.mBadge = (ImageView) findViewById(R.id.badge);
+ this.mTitle = (TextView) findViewById(R.id.title);
+ this.mRatingText = (TextView) findViewById(R.id.rating);
+ this.mRatingView = (RatingView) findViewById(R.id.rating_view);
+ this.mPrice = (TextView) findViewById(R.id.price);
+ this.mReviewCount = (TextView) findViewById(R.id.review_count);
+ }
+
+ public void init(OnClickListener clickListener,
+ AccessibilityDelegate accessibilityDelegate,
+ OnLongClickListener onLongClickListener) {
+ setOnClickListener(clickListener);
+ mImage.setOnClickListener(clickListener);
+ setAccessibilityDelegate(accessibilityDelegate);
+ mOnLongClickListener = onLongClickListener;
+ }
+
+ public void apply(@NonNull AppDiscoveryAppInfo info) {
+ setTag(info);
+ mImage.setTag(info);
+ mImage.setImageBitmap(info.iconBitmap);
+ mImage.setOnLongClickListener(info.isDragAndDropSupported() ? mOnLongClickListener : null);
+ mBadge.setVisibility(info.isInstantApp ? View.VISIBLE : View.GONE);
+ mTitle.setText(info.title);
+ mPrice.setText(info.priceFormatted != null ? info.priceFormatted : "");
+ mReviewCount.setVisibility(SHOW_REVIEW_COUNT ? View.VISIBLE : View.GONE);
+ if (info.rating >= 0) {
+ mRatingText.setText(new DecimalFormat("#.#").format(info.rating));
+ mRatingView.setRating(info.rating);
+ mRatingView.setVisibility(View.VISIBLE);
+ String reviewCountFormatted = NumberFormat.getInstance().format(info.reviewCount);
+ mReviewCount.setText("(" + reviewCountFormatted + ")");
+ } else {
+ // if we don't have a rating
+ mRatingView.setVisibility(View.GONE);
+ mRatingText.setText("");
+ mReviewCount.setText("");
+ }
+ }
+}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java b/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java
new file mode 100644
index 000000000..0700a1023
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java
@@ -0,0 +1,21 @@
+/*
+ * 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.discovery;
+
+public enum AppDiscoveryUpdateState {
+ START, UPDATE, END
+}
diff --git a/src/com/android/launcher3/discovery/RatingView.java b/src/com/android/launcher3/discovery/RatingView.java
new file mode 100644
index 000000000..8fe63d6ba
--- /dev/null
+++ b/src/com/android/launcher3/discovery/RatingView.java
@@ -0,0 +1,94 @@
+/*
+ * 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.discovery;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.ClipDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+
+import com.android.launcher3.R;
+
+/**
+ * A simple rating view that shows stars with a rating from 0-5.
+ */
+public class RatingView extends View {
+
+ private static final float WIDTH_FACTOR = 0.9f;
+ private static final int MAX_LEVEL = 10000;
+ private static final int MAX_STARS = 5;
+
+ private final Drawable mStarDrawable;
+ private final int mColorGray;
+ private final int mColorHighlight;
+
+ private float rating;
+
+ public RatingView(Context context) {
+ this(context, null);
+ }
+
+ public RatingView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RatingView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mStarDrawable = getResources().getDrawable(R.drawable.ic_star_rating, null);
+ mColorGray = 0x1E000000;
+ mColorHighlight = 0x8A000000;
+ }
+
+ public void setRating(float rating) {
+ this.rating = Math.min(Math.max(rating, 0), MAX_STARS);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ drawStars(canvas, MAX_STARS, mColorGray);
+ drawStars(canvas, rating, mColorHighlight);
+ }
+
+ private void drawStars(Canvas canvas, float stars, int color) {
+ int fullWidth = getLayoutParams().width;
+ int cellWidth = fullWidth / MAX_STARS;
+ int starWidth = (int) (cellWidth * WIDTH_FACTOR);
+ int padding = cellWidth - starWidth;
+ int fullStars = (int) stars;
+ float partialStarFactor = stars - fullStars;
+
+ for (int i = 0; i < fullStars; i++) {
+ int x = i * cellWidth + padding;
+ Drawable star = mStarDrawable.getConstantState().newDrawable().mutate();
+ star.setTint(color);
+ star.setBounds(x, padding, x + starWidth, padding + starWidth);
+ star.draw(canvas);
+ }
+ if (partialStarFactor > 0f) {
+ int x = fullStars * cellWidth + padding;
+ ClipDrawable star = new ClipDrawable(mStarDrawable,
+ Gravity.LEFT, ClipDrawable.HORIZONTAL);
+ star.setTint(color);
+ star.setLevel((int) (MAX_LEVEL * partialStarFactor));
+ star.setBounds(x, padding, x + starWidth, padding + starWidth);
+ star.draw(canvas);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index e4c9be4db..7806c98be 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -24,7 +24,6 @@ import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
@@ -36,6 +35,7 @@ import android.view.animation.DecelerateInterpolator;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
import java.util.Arrays;
@@ -259,7 +259,7 @@ public class DragView extends View {
m1.setSaturation(0);
ColorMatrix m2 = new ColorMatrix();
- setColorScale(color, m2);
+ Themes.setColorScaleOnMatrix(color, m2);
m1.postConcat(m2);
animateFilterTo(m1.getArray());
@@ -384,11 +384,6 @@ public class DragView extends View {
}
}
- public static void setColorScale(int color, ColorMatrix target) {
- target.setScale(Color.red(color) / 255f, Color.green(color) / 255f,
- Color.blue(color) / 255f, Color.alpha(color) / 255f);
- }
-
public int getBlurSizeOutline() {
return mBlurSizeOutline;
}
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 840fcf5fe..503c2ec9f 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -121,6 +121,11 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule
}
@Override
+ public float getIconSize() {
+ return mIconSize;
+ }
+
+ @Override
public int maxNumItems() {
return MAX_NUM_ITEMS_IN_PREVIEW;
}
@@ -129,24 +134,4 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule
public boolean clipToBackground() {
return true;
}
-
- @Override
- public List<View> getItemsToDisplay(Folder folder) {
- List<View> items = new ArrayList<>(folder.getItemsInReadingOrder());
- int numItems = items.size();
- if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION && numItems > MAX_NUM_ITEMS_IN_PREVIEW) {
- // We match the icons in the preview with the layout of the opened folder (b/27944225),
- // but we still need to figure out how we want to handle updating the preview when the
- // upper left quadrant changes.
- int appsPerRow = folder.mContent.getPageAt(0).getCountX();
- int appsToDelete = appsPerRow - MAX_NUM_ITEMS_PER_ROW;
-
- // We only display the upper left quadrant.
- while (appsToDelete > 0) {
- items.remove(MAX_NUM_ITEMS_PER_ROW);
- appsToDelete--;
- }
- }
- return items.subList(0, Math.min(numItems, MAX_NUM_ITEMS_IN_PREVIEW));
- }
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 3d28f2291..15bdea986 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -46,6 +46,7 @@ import android.widget.TextView;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Alarm;
import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
@@ -56,7 +57,6 @@ import com.android.launcher3.FolderInfo.FolderListener;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LogDecelerateInterpolator;
import com.android.launcher3.OnAlarmListener;
@@ -67,22 +67,22 @@ import com.android.launcher3.UninstallDropTarget.DropTargetSource;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace.ItemOperator;
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
+import com.android.launcher3.anim.AnimationLayerSet;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragController.DragListener;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.pageindicators.PageIndicatorDots;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.util.CircleRevealOutlineProvider;
+import com.android.launcher3.anim.CircleRevealOutlineProvider;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.List;
/**
* Represents a set of icons chosen by the user or generated by the system.
@@ -135,8 +135,10 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
@Thunk final ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
+ private FolderAnimationManager mFolderAnimationManager;
+
private final int mExpandDuration;
- private final int mMaterialExpandDuration;
+ public final int mMaterialExpandDuration;
private final int mMaterialExpandStagger;
protected final Launcher mLauncher;
@@ -249,8 +251,11 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
}
mFolderName.setOnEditorActionListener(this);
mFolderName.setSelectAllOnFocus(true);
- mFolderName.setInputType(mFolderName.getInputType() |
- InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ mFolderName.setInputType(mFolderName.getInputType()
+ & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
+ & ~InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
+ | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ mFolderName.forceDisableSuggestions(true);
mFooter = findViewById(R.id.folder_footer);
@@ -474,6 +479,8 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
}
}
});
+
+ mFolderAnimationManager = new FolderAnimationManager(this);
}
/**
@@ -509,51 +516,13 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
mState = STATE_SMALL;
}
- /**
- * Opens the user folder described by the specified tag. The opening of the folder
- * is animated relative to the specified View. If the View is null, no animation
- * is played.
- */
- public void animateOpen() {
- Folder openFolder = getOpen(mLauncher);
- if (openFolder != null && openFolder != this) {
- // Close any open folder before opening a folder.
- openFolder.close(true);
- }
-
- DragLayer dragLayer = mLauncher.getDragLayer();
- // Just verify that the folder hasn't already been added to the DragLayer.
- // There was a one-off crash where the folder had a parent already.
- if (getParent() == null) {
- dragLayer.addView(this);
- mDragController.addDropTarget(this);
- } else {
- if (ProviderConfig.IS_DOGFOOD_BUILD) {
- Log.e(TAG, "Opening folder (" + this + ") which already has a parent:"
- + getParent());
- }
- }
-
- mIsOpen = true;
- mFolderIcon.growAndFadeOut();
-
- mContent.completePendingPageChanges();
- if (!mDragInProgress) {
- // Open on the first page.
- mContent.snapToPageImmediately(0);
- }
-
- // This is set to true in close(), but isn't reset to false until onDropCompleted(). This
- // leads to an inconsistent state if you drag out of the folder and drag back in without
- // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
- mDeleteFolderOnDropCompleted = false;
-
- final Runnable onCompleteRunnable;
+ private AnimatorSet getOpeningAnimatorSet() {
prepareReveal();
- centerAboutIcon();
+ mFolderIcon.growAndFadeOut();
AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
- int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+
+ int width = getFolderWidth();
int height = getFolderHeight();
float transX = - 0.075f * (width / 2 - getPivotX());
@@ -594,13 +563,61 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
anim.play(textAlpha);
anim.play(reveal);
- mContent.setLayerType(LAYER_TYPE_HARDWARE, null);
- mFooter.setLayerType(LAYER_TYPE_HARDWARE, null);
+ AnimationLayerSet layerSet = new AnimationLayerSet();
+ layerSet.addView(mContent);
+ layerSet.addView(mFooter);
+ anim.addListener(layerSet);
+
+ return anim;
+ }
+
+ /**
+ * Opens the user folder described by the specified tag. The opening of the folder
+ * is animated relative to the specified View. If the View is null, no animation
+ * is played.
+ */
+ public void animateOpen() {
+ Folder openFolder = getOpen(mLauncher);
+ if (openFolder != null && openFolder != this) {
+ // Close any open folder before opening a folder.
+ openFolder.close(true);
+ }
+
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ // Just verify that the folder hasn't already been added to the DragLayer.
+ // There was a one-off crash where the folder had a parent already.
+ if (getParent() == null) {
+ dragLayer.addView(this);
+ mDragController.addDropTarget(this);
+ } else {
+ if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ Log.e(TAG, "Opening folder (" + this + ") which already has a parent:"
+ + getParent());
+ }
+ }
+
+ mIsOpen = true;
+
+ mContent.completePendingPageChanges();
+ if (!mDragInProgress) {
+ // Open on the first page.
+ mContent.snapToPageImmediately(0);
+ }
+
+ // This is set to true in close(), but isn't reset to false until onDropCompleted(). This
+ // leads to an inconsistent state if you drag out of the folder and drag back in without
+ // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
+ mDeleteFolderOnDropCompleted = false;
+
+ final Runnable onCompleteRunnable;
+ centerAboutIcon();
+
+ AnimatorSet anim = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION
+ ? mFolderAnimationManager.getOpeningAnimator()
+ : getOpeningAnimatorSet();
onCompleteRunnable = new Runnable() {
@Override
public void run() {
- mContent.setLayerType(LAYER_TYPE_NONE, null);
- mFooter.setLayerType(LAYER_TYPE_NONE, null);
mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
}
};
@@ -695,7 +712,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
mFolderName.dispatchBackKey();
}
- if (mFolderIcon != null) {
+ if (mFolderIcon != null && !FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) {
mFolderIcon.shrinkAndFadeIn(animate);
}
@@ -713,12 +730,24 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
parent.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
+ private AnimatorSet getClosingAnimatorSet() {
+ AnimatorSet animatorSet = LauncherAnimUtils.createAnimatorSet();
+ animatorSet.play(LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f));
+
+ AnimationLayerSet layerSet = new AnimationLayerSet();
+ layerSet.addView(this);
+ animatorSet.addListener(layerSet);
+ animatorSet.setDuration(mExpandDuration);
+ return animatorSet;
+ }
+
private void animateClosed() {
- final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f);
- oa.addListener(new AnimatorListenerAdapter() {
+ AnimatorSet a = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION
+ ? mFolderAnimationManager.getClosingAnimator()
+ : getClosingAnimatorSet();
+ a.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- setLayerType(LAYER_TYPE_NONE, null);
closeComplete(true);
}
@Override
@@ -730,9 +759,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
mState = STATE_ANIMATING;
}
});
- oa.setDuration(mExpandDuration);
- setLayerType(LAYER_TYPE_HARDWARE, null);
- oa.start();
+ a.start();
}
private void closeComplete(boolean wasAnimated) {
@@ -743,10 +770,14 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
}
mDragController.removeDropTarget(this);
clearFocus();
- if (wasAnimated) {
- mFolderIcon.requestFocus();
+ if (mFolderIcon != null) {
+ mFolderIcon.setVisibility(View.VISIBLE);
+ if (wasAnimated) {
+ mFolderIcon.requestFocus();
+ }
}
+
if (mRearrangeOnClose) {
rearrangeChildren();
mRearrangeOnClose = false;
@@ -1046,7 +1077,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer);
- int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+ int width = getFolderWidth();
int height = getFolderHeight();
float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, sTempRect);
@@ -1088,6 +1119,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
int folderPivotY = height / 2 + (centeredTop - top);
setPivotX(folderPivotX);
setPivotY(folderPivotY);
+
mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
(1.0f * folderPivotX / width));
mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
@@ -1119,6 +1151,10 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
return Math.max(mContent.getDesiredWidth(), MIN_CONTENT_DIMEN);
}
+ private int getFolderWidth() {
+ return getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+ }
+
private int getFolderHeight() {
return getFolderHeight(getContentAreaHeight());
}
@@ -1405,6 +1441,11 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
updateTextViewFocus();
}
+ @Override
+ public void prepareAutoUpdate() {
+ close(false);
+ }
+
public void onTitleChanged(CharSequence title) {
}
@@ -1424,6 +1465,26 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
return mItemsInReadingOrder;
}
+ public List<BubbleTextView> getItemsOnCurrentPage() {
+ ArrayList<View> allItems = getItemsInReadingOrder();
+ int currentPage = mContent.getCurrentPage();
+ int lastPage = mContent.getPageCount() - 1;
+ int totalItemsInFolder = allItems.size();
+ int itemsPerPage = mContent.itemsPerPage();
+ int numItemsOnCurrentPage = currentPage == lastPage
+ ? totalItemsInFolder - (itemsPerPage * currentPage)
+ : itemsPerPage;
+
+ int startIndex = currentPage * itemsPerPage;
+ int endIndex = startIndex + numItemsOnCurrentPage;
+
+ List<BubbleTextView> itemsOnCurrentPage = new ArrayList<>(numItemsOnCurrentPage);
+ for (int i = startIndex; i < endIndex; ++i) {
+ itemsOnCurrentPage.add((BubbleTextView) allItems.get(i));
+ }
+ return itemsOnCurrentPage;
+ }
+
public void onFocusChange(View v, boolean hasFocus) {
if (v == mFolderName) {
if (hasFocus) {
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
new file mode 100644
index 000000000..6ce572d94
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -0,0 +1,357 @@
+/*
+ * 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.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.GradientDrawable;
+import android.support.v4.graphics.ColorUtils;
+import android.util.Property;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.util.Themes;
+
+import java.util.List;
+
+/**
+ * Manages the opening and closing animations for a {@link Folder}.
+ *
+ * All of the animations are done in the Folder.
+ * ie. When the user taps on the FolderIcon, we immediately hide the FolderIcon and show the Folder
+ * in its place before starting the animation.
+ */
+public class FolderAnimationManager {
+
+ private Folder mFolder;
+ private FolderPagedView mContent;
+ private GradientDrawable mFolderBackground;
+
+ private FolderIcon mFolderIcon;
+ private FolderIcon.PreviewBackground mPreviewBackground;
+
+ private Context mContext;
+ private Launcher mLauncher;
+
+ private Animator mRevealAnimator;
+ private final TimeInterpolator mOpeningInterpolator;
+ private final TimeInterpolator mClosingInterpolator;
+ private final TimeInterpolator mPreviewItemOpeningInterpolator;
+ private final TimeInterpolator mPreviewItemClosingInterpolator;
+
+ private final FolderIcon.PreviewItemDrawingParams mTmpParams =
+ new FolderIcon.PreviewItemDrawingParams(0, 0, 0, 0);
+
+ private static final Property<View, Float> SCALE_PROPERTY =
+ new Property<View, Float>(Float.class, "scale") {
+ @Override
+ public Float get(View view) {
+ return view.getScaleX();
+ }
+
+ @Override
+ public void set(View view, Float scale) {
+ view.setScaleX(scale);
+ view.setScaleY(scale);
+ }
+ };
+
+ private static final Property<List<BubbleTextView>, Integer> ITEMS_TEXT_COLOR_PROPERTY =
+ new Property<List<BubbleTextView>, Integer>(Integer.class, "textColor") {
+ @Override
+ public Integer get(List<BubbleTextView> items) {
+ return items.get(0).getCurrentTextColor();
+ }
+
+ @Override
+ public void set(List<BubbleTextView> items, Integer color) {
+ int size = items.size();
+
+ for (int i = 0; i < size; ++i) {
+ items.get(i).setTextColor(color);
+ }
+ }
+ };
+
+ public FolderAnimationManager(Folder folder) {
+ mFolder = folder;
+ mContent = folder.mContent;
+ mFolderBackground = (GradientDrawable) mFolder.getBackground();
+
+ mFolderIcon = folder.mFolderIcon;
+ mPreviewBackground = mFolderIcon.mBackground;
+
+ mContext = folder.getContext();
+ mLauncher = folder.mLauncher;
+
+ mOpeningInterpolator = AnimationUtils.loadInterpolator(mContext,
+ R.interpolator.folder_opening_interpolator);
+ mClosingInterpolator = AnimationUtils.loadInterpolator(mContext,
+ R.interpolator.folder_closing_interpolator);
+ mPreviewItemOpeningInterpolator = AnimationUtils.loadInterpolator(mContext,
+ R.interpolator.folder_preview_item_opening_interpolator);
+ mPreviewItemClosingInterpolator = AnimationUtils.loadInterpolator(mContext,
+ R.interpolator.folder_preview_item_closing_interpolator);
+ }
+
+ public AnimatorSet getOpeningAnimator() {
+ mFolder.setPivotX(0);
+ mFolder.setPivotY(0);
+
+ AnimatorSet a = getAnimatorSet(true /* isOpening */);
+ a.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mFolderIcon.setVisibility(View.INVISIBLE);
+ }
+ });
+ return a;
+ }
+
+ public AnimatorSet getClosingAnimator() {
+ AnimatorSet a = getAnimatorSet(false /* isOpening */);
+ return a;
+ }
+
+ /**
+ * Prepares the Folder for animating between open / closed states.
+ *
+ * @param isOpening If true, return the animator set for the opening animation.
+ */
+ private AnimatorSet getAnimatorSet(final boolean isOpening) {
+ final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams();
+ FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule();
+ final List<BubbleTextView> itemsInPreview = mFolderIcon.getItemsToDisplay();
+
+ // Match size/scale of icons in the preview
+ float previewScale = rule.scaleForItem(0, itemsInPreview.size());
+ float previewSize = rule.getIconSize() * previewScale;
+ float folderScale = previewSize / itemsInPreview.get(0).getIconSize();
+
+ final float initialScale = folderScale;
+ final float finalScale = 1f;
+ float scale = isOpening ? initialScale : finalScale;
+ mFolder.setScaleX(scale);
+ mFolder.setScaleY(scale);
+
+ // Match position of the FolderIcon
+ final Rect folderIconPos = new Rect();
+ float scaleRelativeToDragLayer = mLauncher.getDragLayer()
+ .getDescendantRectRelativeToSelf(mFolderIcon, folderIconPos);
+ folderScale *= scaleRelativeToDragLayer;
+
+ // We want to create a small X offset for the preview items, so that they follow their
+ // expected path to their final locations. ie. an icon should not move right, if it's final
+ // location is to its left. This value is arbitrarily defined.
+ final int nudgeOffsetX = (int) (previewSize / 2);
+
+ final int paddingOffsetX = (int) ((mFolder.getPaddingLeft() + mContent.getPaddingLeft())
+ * folderScale);
+ final int paddingOffsetY = (int) ((mFolder.getPaddingTop() + mContent.getPaddingTop())
+ * folderScale);
+
+ int initialX = folderIconPos.left + mFolderIcon.mBackground.getOffsetX() - paddingOffsetX
+ - nudgeOffsetX;
+ int initialY = folderIconPos.top + mFolderIcon.mBackground.getOffsetY() - paddingOffsetY;
+ final float xDistance = initialX - lp.x;
+ final float yDistance = initialY - lp.y;
+
+ // Set up the Folder background.
+ final int finalColor = Themes.getAttrColor(mContext, android.R.attr.colorPrimary);
+ final int initialColor =
+ ColorUtils.setAlphaComponent(finalColor, mPreviewBackground.getBackgroundAlpha());
+ mFolderBackground.setColor(isOpening ? initialColor : finalColor);
+
+ // Initialize the Folder items' text.
+ final List<BubbleTextView> itemsOnCurrentPage = mFolder.getItemsOnCurrentPage();
+ final int finalTextColor = Themes.getAttrColor(mContext, android.R.attr.textColorSecondary);
+ ITEMS_TEXT_COLOR_PROPERTY.set(itemsOnCurrentPage, isOpening ? Color.TRANSPARENT
+ : finalTextColor);
+
+ // Create the animators.
+ AnimatorSet a = LauncherAnimUtils.createAnimatorSet();
+ a.setDuration(mFolder.mMaterialExpandDuration);
+
+ ObjectAnimator translationX = isOpening
+ ? ObjectAnimator.ofFloat(mFolder, View.TRANSLATION_X, xDistance, 0)
+ : ObjectAnimator.ofFloat(mFolder, View.TRANSLATION_X, 0, xDistance);
+ a.play(translationX);
+
+ ObjectAnimator translationY = isOpening
+ ? ObjectAnimator.ofFloat(mFolder, View.TRANSLATION_Y, yDistance, 0)
+ : ObjectAnimator.ofFloat(mFolder, View.TRANSLATION_Y, 0, yDistance);
+ a.play(translationY);
+
+ ObjectAnimator scaleAnimator = isOpening
+ ? ObjectAnimator.ofFloat(mFolder, SCALE_PROPERTY, initialScale, finalScale)
+ : ObjectAnimator.ofFloat(mFolder, SCALE_PROPERTY, finalScale, initialScale);
+ a.play(scaleAnimator);
+
+ ObjectAnimator itemsTextColor = isOpening
+ ? ObjectAnimator.ofArgb(itemsOnCurrentPage, ITEMS_TEXT_COLOR_PROPERTY,
+ Color.TRANSPARENT, finalTextColor)
+ : ObjectAnimator.ofArgb(itemsOnCurrentPage, ITEMS_TEXT_COLOR_PROPERTY,
+ finalTextColor, Color.TRANSPARENT);
+ a.play(itemsTextColor);
+
+ ObjectAnimator backgroundColor = isOpening
+ ? ObjectAnimator.ofArgb(mFolderBackground, "color", initialColor, finalColor)
+ : ObjectAnimator.ofArgb(mFolderBackground, "color", finalColor, initialColor);
+ a.play(backgroundColor);
+
+ // Set up the reveal animation that clips the Folder.
+ float stroke = mPreviewBackground.getStrokeWidth();
+ int initialSize = (int) ((mFolderIcon.mBackground.getRadius() * 2 + stroke) / folderScale);
+ int totalOffsetX = paddingOffsetX + Math.round(nudgeOffsetX / folderScale);
+ int unscaledStroke = (int) Math.floor(stroke / folderScale);
+ Rect startRect = new Rect(totalOffsetX + unscaledStroke, unscaledStroke,
+ totalOffsetX + initialSize, initialSize);
+ Rect endRect = new Rect(0, 0, lp.width, lp.height);
+ a.play(getRevealAnimator(isOpening, initialSize / 2f, startRect, endRect));
+
+ a.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ ITEMS_TEXT_COLOR_PROPERTY.set(itemsOnCurrentPage, finalTextColor);
+ }
+ });
+
+ // We set the interpolator on all current child animators here, because the preview item
+ // animators may use a different interpolator.
+ for (Animator animator : a.getChildAnimations()) {
+ animator.setInterpolator(isOpening ? mOpeningInterpolator : mClosingInterpolator);
+ }
+
+ addPreviewItemAnimatorsToSet(a, isOpening, folderScale, nudgeOffsetX);
+ return a;
+ }
+
+ private Animator getRevealAnimator(boolean isOpening, float circleRadius, Rect start,
+ Rect end) {
+ boolean revealIsRunning = mRevealAnimator != null && mRevealAnimator.isRunning();
+ final float finalRadius = revealIsRunning
+ ? ((RoundedRectRevealOutlineProvider) mFolder.getOutlineProvider()).getRadius()
+ : Utilities.pxFromDp(2, mContext.getResources().getDisplayMetrics());
+ if (revealIsRunning) {
+ mRevealAnimator.cancel();
+ }
+ mRevealAnimator = new RoundedRectRevealOutlineProvider(circleRadius, finalRadius,
+ start, end).createRevealAnimator(mFolder, !isOpening);
+ return mRevealAnimator;
+ }
+
+ /**
+ * Animate the items that are displayed in the preview.
+ */
+ private void addPreviewItemAnimatorsToSet(AnimatorSet animatorSet, boolean isOpening,
+ final float folderScale, int nudgeOffsetX) {
+ FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule();
+ final List<BubbleTextView> itemsInPreview = mFolderIcon.getItemsToDisplay();
+ final int numItemsInPreview = itemsInPreview.size();
+
+ TimeInterpolator previewItemInterpolator = getPreviewItemInterpolator(isOpening);
+
+ ShortcutAndWidgetContainer cwc = mContent.getPageAt(0).getShortcutsAndWidgets();
+ for (int i = 0; i < numItemsInPreview; ++i) {
+ final BubbleTextView btv = itemsInPreview.get(i);
+ CellLayout.LayoutParams btvLp = (CellLayout.LayoutParams) btv.getLayoutParams();
+
+ // Calculate the final values in the LayoutParams.
+ btvLp.isLockedToGrid = true;
+ cwc.setupLp(btv);
+
+ // Match scale of icons in the preview.
+ float previewScale = rule.scaleForItem(i, numItemsInPreview);
+ float previewSize = rule.getIconSize() * previewScale;
+ float iconScale = previewSize / itemsInPreview.get(i).getIconSize();
+
+ final float initialScale = iconScale / folderScale;
+ final float finalScale = 1f;
+ float scale = isOpening ? initialScale : finalScale;
+ btv.setScaleX(scale);
+ btv.setScaleY(scale);
+
+ // Match positions of the icons in the folder with their positions in the preview
+ rule.computePreviewItemDrawingParams(i, numItemsInPreview, 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 + nudgeOffsetX) / folderScale);
+ final int previewPosY = (int) (mTmpParams.transY / folderScale);
+
+ final float xDistance = previewPosX - btvLp.x;
+ final float yDistance = previewPosY - btvLp.y;
+
+ ObjectAnimator translationX = isOpening
+ ? ObjectAnimator.ofFloat(btv, View.TRANSLATION_X, xDistance, 0)
+ : ObjectAnimator.ofFloat(btv, View.TRANSLATION_X, 0, xDistance);
+ translationX.setInterpolator(previewItemInterpolator);
+ animatorSet.play(translationX);
+
+ ObjectAnimator translationY = isOpening
+ ? ObjectAnimator.ofFloat(btv, View.TRANSLATION_Y, yDistance, 0)
+ : ObjectAnimator.ofFloat(btv, View.TRANSLATION_Y, 0, yDistance);
+ translationY.setInterpolator(previewItemInterpolator);
+ animatorSet.play(translationY);
+
+ ObjectAnimator scaleAnimator = isOpening
+ ? ObjectAnimator.ofFloat(btv, SCALE_PROPERTY, initialScale, finalScale)
+ : ObjectAnimator.ofFloat(btv, SCALE_PROPERTY, finalScale, initialScale);
+ scaleAnimator.setInterpolator(previewItemInterpolator);
+ animatorSet.play(scaleAnimator);
+
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ btv.setTranslationX(0.0f);
+ btv.setTranslationY(0.0f);
+ btv.setScaleX(1f);
+ btv.setScaleY(1f);
+ }
+ });
+ }
+ }
+
+ private TimeInterpolator getPreviewItemInterpolator(boolean isOpening) {
+ if (mFolder.getItemCount() > FolderIcon.NUM_ITEMS_IN_PREVIEW) {
+ // With larger folders, we want the preview items to reach their final positions faster
+ // (when opening) and later (when closing) so that they appear aligned with the rest of
+ // the folder items when they are both visible.
+ return isOpening ? mPreviewItemOpeningInterpolator : mPreviewItemClosingInterpolator;
+ }
+ return isOpening ? mOpeningInterpolator : mClosingInterpolator;
+ }
+}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 604540022..ced9c9e8d 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -24,11 +24,15 @@ import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.Region;
+import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.os.Parcelable;
import android.util.AttributeSet;
@@ -121,12 +125,11 @@ public class FolderIcon extends FrameLayout implements FolderListener {
private float mSlop;
+ FolderIconPreviewVerifier mPreviewVerifier;
private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
private ArrayList<PreviewItemDrawingParams> mDrawingParams = new ArrayList<PreviewItemDrawingParams>();
private Drawable mReferenceDrawable = null;
- Paint mBgPaint = new Paint();
-
private Alarm mOpenAlarm = new Alarm();
private FolderBadgeInfo mBadgeInfo = new FolderBadgeInfo();
@@ -180,11 +183,6 @@ public class FolderIcon extends FrameLayout implements FolderListener {
FolderIcon icon = (FolderIcon) LayoutInflater.from(group.getContext())
.inflate(resId, group, false);
- // For performance and compatibility reasons we render the preview using a software layer.
- // In particular, hardware path clipping has spotty ecosystem support and bad performance.
- // Software rendering also allows us to use shadow layers.
- icon.setLayerType(LAYER_TYPE_SOFTWARE, new Paint(Paint.FILTER_BITMAP_FLAG));
-
icon.setClipToPadding(false);
icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
icon.mFolderName.setText(folderInfo.title);
@@ -223,6 +221,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
private void setFolder(Folder folder) {
mFolder = folder;
+ mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
updateItemDrawingParams(false);
}
@@ -409,6 +408,10 @@ public class FolderIcon extends FrameLayout implements FolderListener {
mBadgeInfo = badgeInfo;
}
+ public PreviewLayoutRule getLayoutRule() {
+ return mPreviewLayoutRule;
+ }
+
/**
* Sets mBadgeScale to 1 or 0, animating if oldCount or newCount is 0
* (the badge is being added or removed).
@@ -494,7 +497,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
- canvas.save();
+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(params.transX, params.transY);
canvas.scale(params.scale, params.scale);
Drawable d = params.drawable;
@@ -521,10 +524,29 @@ public class FolderIcon extends FrameLayout implements FolderListener {
* information, handles drawing, and animation (accept state <--> rest state).
*/
public static class PreviewBackground {
+
+ private final PorterDuffXfermode mClipPorterDuffXfermode
+ = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
+ // Create a RadialGradient such that it draws a black circle and then extends with
+ // transparent. To achieve this, we keep the gradient to black for the range [0, 1) and
+ // just at the edge quickly change it to transparent.
+ private final RadialGradient mClipShader = new RadialGradient(0, 0, 1,
+ new int[] {Color.BLACK, Color.BLACK, Color.TRANSPARENT },
+ new float[] {0, 0.999f, 1},
+ Shader.TileMode.CLAMP);
+
+ private final PorterDuffXfermode mShadowPorterDuffXfermode
+ = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
+ private RadialGradient mShadowShader = null;
+
+ private final Matrix mShaderMatrix = new Matrix();
+ private final Path mPath = new Path();
+
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
private float mScale = 1f;
private float mColorMultiplier = 1f;
- private Path mClipPath = new Path();
- private int mStrokeWidth;
+ private float mStrokeWidth;
private View mInvalidateDelegate;
public int previewSize;
@@ -547,7 +569,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
private static final int BG_OPACITY = 160;
private static final int MAX_BG_OPACITY = 225;
private static final int BG_INTENSITY = 245;
- private static final int SHADOW_OPACITY = 80;
+ private static final int SHADOW_OPACITY = 40;
ValueAnimator mScaleAnimator;
@@ -563,7 +585,16 @@ public class FolderIcon extends FrameLayout implements FolderListener {
basePreviewOffsetX = (availableSpace - this.previewSize) / 2;
basePreviewOffsetY = previewPadding + grid.folderBackgroundOffset + topPadding;
- mStrokeWidth = Utilities.pxFromDp(1, dm);
+ // Stroke width is 1dp
+ mStrokeWidth = dm.density;
+
+ float radius = getScaledRadius();
+ float shadowRadius = radius + mStrokeWidth;
+ int shadowColor = Color.argb(SHADOW_OPACITY, 0, 0, 0);
+ mShadowShader = new RadialGradient(0, 0, 1,
+ new int[] {shadowColor, Color.TRANSPARENT},
+ new float[] {radius / shadowRadius, 1},
+ Shader.TileMode.CLAMP);
invalidate();
}
@@ -593,10 +624,6 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
void invalidate() {
- int radius = getScaledRadius();
- mClipPath.reset();
- mClipPath.addCircle(radius, radius, radius, Path.Direction.CW);
-
if (mInvalidateDelegate != null) {
mInvalidateDelegate.invalidate();
}
@@ -611,70 +638,94 @@ public class FolderIcon extends FrameLayout implements FolderListener {
invalidate();
}
- public void drawBackground(Canvas canvas, Paint paint) {
- canvas.save();
- canvas.translate(getOffsetX(), getOffsetY());
-
- paint.reset();
- paint.setStyle(Paint.Style.FILL);
- paint.setXfermode(null);
- paint.setAntiAlias(true);
-
+ public void drawBackground(Canvas canvas) {
+ mPaint.setStyle(Paint.Style.FILL);
int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
- paint.setColor(Color.argb(alpha, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
+ mPaint.setColor(Color.argb(alpha, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
+
+ drawCircle(canvas, 0 /* deltaRadius */);
+ // Draw shadow.
+ if (mShadowShader == null) {
+ return;
+ }
float radius = getScaledRadius();
+ float shadowRadius = radius + mStrokeWidth;
+ mPaint.setColor(Color.BLACK);
+ int offsetX = getOffsetX();
+ int offsetY = getOffsetY();
+ final int saveCount;
+ if (canvas.isHardwareAccelerated()) {
+ saveCount = canvas.saveLayer(offsetX - mStrokeWidth, offsetY,
+ offsetX + radius + shadowRadius, offsetY + shadowRadius + shadowRadius,
+ null, Canvas.CLIP_TO_LAYER_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
- canvas.drawCircle(radius, radius, radius, paint);
- canvas.clipPath(mClipPath, Region.Op.DIFFERENCE);
+ } else {
+ saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+ clipCanvasSoftware(canvas, Region.Op.DIFFERENCE);
+ }
- paint.setStyle(Paint.Style.STROKE);
- paint.setColor(Color.TRANSPARENT);
- paint.setShadowLayer(mStrokeWidth, 0, mStrokeWidth, Color.argb(SHADOW_OPACITY, 0, 0, 0));
- canvas.drawCircle(radius, radius, radius, paint);
+ mShaderMatrix.setScale(shadowRadius, shadowRadius);
+ mShaderMatrix.postTranslate(radius + offsetX, shadowRadius + offsetY);
+ mShadowShader.setLocalMatrix(mShaderMatrix);
+ mPaint.setShader(mShadowShader);
+ canvas.drawPaint(mPaint);
+ mPaint.setShader(null);
+
+ if (canvas.isHardwareAccelerated()) {
+ mPaint.setXfermode(mShadowPorterDuffXfermode);
+ canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint);
+ mPaint.setXfermode(null);
+ }
- canvas.restore();
+ canvas.restoreToCount(saveCount);
}
- public void drawBackgroundStroke(Canvas canvas, Paint paint) {
- canvas.save();
- canvas.translate(getOffsetX(), getOffsetY());
-
- paint.reset();
- paint.setAntiAlias(true);
- paint.setColor(Color.argb(255, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
- paint.setStyle(Paint.Style.STROKE);
- paint.setStrokeWidth(mStrokeWidth);
-
- float radius = getScaledRadius();
- canvas.drawCircle(radius, radius, radius - 1, paint);
-
- canvas.restore();
+ public void drawBackgroundStroke(Canvas canvas) {
+ mPaint.setColor(Color.argb(255, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(mStrokeWidth);
+ drawCircle(canvas, 1 /* deltaRadius */);
}
- public void drawLeaveBehind(Canvas canvas, Paint paint) {
+ public void drawLeaveBehind(Canvas canvas) {
float originalScale = mScale;
mScale = 0.5f;
- canvas.save();
- canvas.translate(getOffsetX(), getOffsetY());
+ mPaint.setStyle(Paint.Style.FILL);
+ mPaint.setColor(Color.argb(160, 245, 245, 245));
+ drawCircle(canvas, 0 /* deltaRadius */);
- paint.reset();
- paint.setAntiAlias(true);
- paint.setColor(Color.argb(160, 245, 245, 245));
+ mScale = originalScale;
+ }
+ private void drawCircle(Canvas canvas,float deltaRadius) {
float radius = getScaledRadius();
- canvas.drawCircle(radius, radius, radius, paint);
+ canvas.drawCircle(radius + getOffsetX(), radius + getOffsetY(),
+ radius - deltaRadius, mPaint);
+ }
- canvas.restore();
- mScale = originalScale;
+ // It is the callers responsibility to save and restore the canvas layers.
+ private void clipCanvasSoftware(Canvas canvas, Region.Op op) {
+ mPath.reset();
+ float r = getScaledRadius();
+ mPath.addCircle(r + getOffsetX(), r + getOffsetY(), r, Path.Direction.CW);
+ canvas.clipPath(mPath, op);
}
- // It is the callers responsibility to save and restore the canvas.
- private void clipCanvas(Canvas canvas) {
- canvas.translate(getOffsetX(), getOffsetY());
- canvas.clipPath(mClipPath);
- canvas.translate(-getOffsetX(), -getOffsetY());
+ // It is the callers responsibility to save and restore the canvas layers.
+ private void clipCanvasHardware(Canvas canvas) {
+ mPaint.setColor(Color.BLACK);
+ mPaint.setXfermode(mClipPorterDuffXfermode);
+
+ float radius = getScaledRadius();
+ mShaderMatrix.setScale(radius, radius);
+ mShaderMatrix.postTranslate(radius + getOffsetX(), radius + getOffsetY());
+ mClipShader.setLocalMatrix(mShaderMatrix);
+ mPaint.setShader(mClipShader);
+ canvas.drawPaint(mPaint);
+ mPaint.setXfermode(null);
+ mPaint.setShader(null);
}
private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
@@ -778,6 +829,14 @@ public class FolderIcon extends FrameLayout implements FolderListener {
};
animateScale(1f, 1f, onStart, onEnd);
}
+
+ public int getBackgroundAlpha() {
+ return (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
+ }
+
+ public float getStrokeWidth() {
+ return mStrokeWidth;
+ }
}
public void setFolderBackground(PreviewBackground bg) {
@@ -794,17 +853,22 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
if (!mBackground.drawingDelegated()) {
- mBackground.drawBackground(canvas, mBgPaint);
+ mBackground.drawBackground(canvas);
}
if (mFolder == null) return;
if (mFolder.getItemCount() == 0 && !mAnimating) return;
- canvas.save();
+ final int saveCount;
-
- if (mPreviewLayoutRule.clipToBackground()) {
- mBackground.clipCanvas(canvas);
+ if (canvas.isHardwareAccelerated()) {
+ saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null,
+ Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+ } else {
+ saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+ if (mPreviewLayoutRule.clipToBackground()) {
+ mBackground.clipCanvasSoftware(canvas, Region.Op.INTERSECT);
+ }
}
// The items are drawn in coordinates relative to the preview offset
@@ -817,18 +881,24 @@ public class FolderIcon extends FrameLayout implements FolderListener {
drawPreviewItem(canvas, p);
}
}
- canvas.restore();
+ canvas.translate(-mBackground.basePreviewOffsetX, -mBackground.basePreviewOffsetY);
+
+ if (mPreviewLayoutRule.clipToBackground() && canvas.isHardwareAccelerated()) {
+ mBackground.clipCanvasHardware(canvas);
+ }
+ canvas.restoreToCount(saveCount);
if (mPreviewLayoutRule.clipToBackground() && !mBackground.drawingDelegated()) {
- mBackground.drawBackgroundStroke(canvas, mBgPaint);
+ mBackground.drawBackgroundStroke(canvas);
}
- int offsetX = mBackground.getOffsetX();
- int offsetY = mBackground.getOffsetY();
- int previewSize = (int) (mBackground.previewSize * mBackground.mScale);
- Rect bounds = new Rect(offsetX, offsetY, offsetX + previewSize, offsetY + previewSize);
if ((mBadgeInfo != null && mBadgeInfo.getNotificationCount() > 0) || mBadgeScale > 0) {
// If we are animating to the accepting state, animate the badge out.
+ int offsetX = mBackground.getOffsetX();
+ int offsetY = mBackground.getOffsetY();
+ int previewSize = (int) (mBackground.previewSize * mBackground.mScale);
+ Rect bounds = new Rect(offsetX, offsetY, offsetX + previewSize, offsetY + previewSize);
+
float badgeScale = Math.max(0, mBadgeScale - mBackground.getScaleProgress());
mBadgeRenderer.draw(canvas, IconPalette.FOLDER_ICON_PALETTE, mBadgeInfo, bounds, badgeScale);
}
@@ -934,8 +1004,26 @@ public class FolderIcon extends FrameLayout implements FolderListener {
return mFolderName.getVisibility() == VISIBLE;
}
+ public List<BubbleTextView> getItemsToDisplay() {
+ mPreviewVerifier.setFolderInfo(mFolder.getInfo());
+
+ List<BubbleTextView> itemsToDisplay = new ArrayList<>();
+ List<View> allItems = mFolder.getItemsInReadingOrder();
+ int numItems = allItems.size();
+ for (int rank = 0; rank < numItems; ++rank) {
+ if (mPreviewVerifier.isItemInPreview(rank)) {
+ itemsToDisplay.add((BubbleTextView) allItems.get(rank));
+ }
+
+ if (itemsToDisplay.size() == FolderIcon.NUM_ITEMS_IN_PREVIEW) {
+ break;
+ }
+ }
+ return itemsToDisplay;
+ }
+
private void updateItemDrawingParams(boolean animate) {
- List<View> items = mPreviewLayoutRule.getItemsToDisplay(mFolder);
+ List<BubbleTextView> items = getItemsToDisplay();
int nItemsInPreview = items.size();
int prevNumItems = mDrawingParams.size();
@@ -950,7 +1038,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
for (int i = 0; i < mDrawingParams.size(); i++) {
PreviewItemDrawingParams p = mDrawingParams.get(i);
- p.drawable = ((TextView) items.get(i)).getCompoundDrawables()[1];
+ p.drawable = items.get(i).getCompoundDrawables()[1];
if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) {
computePreviewItemDrawingParams(i, nItemsInPreview, p);
@@ -982,6 +1070,10 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
@Override
+ public void prepareAutoUpdate() {
+ }
+
+ @Override
public void onAdd(ShortcutInfo item) {
int oldCount = mBadgeInfo.getNotificationCount();
mBadgeInfo.addBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item));
@@ -1115,8 +1207,8 @@ public class FolderIcon extends FrameLayout implements FolderListener {
PreviewItemDrawingParams params);
void init(int availableSpace, int intrinsicIconSize, boolean rtl);
float scaleForItem(int index, int totalNumItems);
+ float getIconSize();
int maxNumItems();
boolean clipToBackground();
- List<View> getItemsToDisplay(Folder folder);
}
}
diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
new file mode 100644
index 000000000..de962b021
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
@@ -0,0 +1,64 @@
+/*
+ * 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 com.android.launcher3.FolderInfo;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.config.FeatureFlags;
+
+/**
+ * Verifies whether an item in a Folder is displayed in the FolderIcon preview.
+ */
+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) {
+ mMaxGridCountX = profile.numFolderColumns;
+ mMaxGridCountY = profile.numFolderRows;
+ mMaxItemsPerPage = mMaxGridCountX * mMaxGridCountY;
+ }
+
+ public void setFolderInfo(FolderInfo info) {
+ int numItemsInFolder = info.contents.size();
+ mDisplayingUpperLeftQuadrant = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION
+ && !FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON
+ && numItemsInFolder > FolderIcon.NUM_ITEMS_IN_PREVIEW;
+
+ if (mDisplayingUpperLeftQuadrant) {
+ FolderPagedView.calculateGridSize(info.contents.size(), 0, 0, mMaxGridCountX,
+ mMaxGridCountY, mMaxItemsPerPage, mGridSize);
+ mGridCountX = mGridSize[0];
+ }
+ }
+
+ public boolean isItemInPreview(int rank) {
+ if (mDisplayingUpperLeftQuadrant) {
+ // Returns true iff the icon is in the 2x2 upper left quadrant of the Folder.
+ 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 532e5a6c7..2a6007a4e 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -65,7 +65,7 @@ public class FolderPagedView extends PagedView {
*/
private static final float SCROLL_HINT_FRACTION = 0.07f;
- private static final int[] sTempPosArray = new int[2];
+ private static final int[] sTmpArray = new int[2];
public final boolean mIsRtl;
@@ -116,40 +116,58 @@ public class FolderPagedView extends PagedView {
}
/**
- * Sets up the grid size such that {@param count} items can fit in the grid.
+ * Calculates the grid size such that {@param count} items can fit in the grid.
* The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
* maintaining the restrictions of {@link #mMaxCountX} &amp; {@link #mMaxCountY}.
*/
- private void setupContentDimensions(int count) {
- mAllocatedContentSize = count;
+ public static void calculateGridSize(int count, int countX, int countY, int maxCountX,
+ int maxCountY, int maxItemsPerPage, int[] out) {
boolean done;
- if (count >= mMaxItemsPerPage) {
- mGridCountX = mMaxCountX;
- mGridCountY = mMaxCountY;
+ int gridCountX = countX;
+ int gridCountY = countY;
+
+ if (count >= maxItemsPerPage) {
+ gridCountX = maxCountX;
+ gridCountY = maxCountY;
done = true;
} else {
done = false;
}
while (!done) {
- int oldCountX = mGridCountX;
- int oldCountY = mGridCountY;
- if (mGridCountX * mGridCountY < count) {
+ int oldCountX = gridCountX;
+ int oldCountY = gridCountY;
+ if (gridCountX * gridCountY < count) {
// Current grid is too small, expand it
- if ((mGridCountX <= mGridCountY || mGridCountY == mMaxCountY) && mGridCountX < mMaxCountX) {
- mGridCountX++;
- } else if (mGridCountY < mMaxCountY) {
- mGridCountY++;
+ if ((gridCountX <= gridCountY || gridCountY == maxCountY)
+ && gridCountX < maxCountX) {
+ gridCountX++;
+ } else if (gridCountY < maxCountY) {
+ gridCountY++;
}
- if (mGridCountY == 0) mGridCountY++;
- } else if ((mGridCountY - 1) * mGridCountX >= count && mGridCountY >= mGridCountX) {
- mGridCountY = Math.max(0, mGridCountY - 1);
- } else if ((mGridCountX - 1) * mGridCountY >= count) {
- mGridCountX = Math.max(0, mGridCountX - 1);
+ if (gridCountY == 0) gridCountY++;
+ } else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) {
+ gridCountY = Math.max(0, gridCountY - 1);
+ } else if ((gridCountX - 1) * gridCountY >= count) {
+ gridCountX = Math.max(0, gridCountX - 1);
}
- done = mGridCountX == oldCountX && mGridCountY == oldCountY;
+ done = gridCountX == oldCountX && gridCountY == oldCountY;
}
+ out[0] = gridCountX;
+ out[1] = gridCountY;
+ }
+
+ /**
+ * Sets up the grid size such that {@param count} items can fit in the grid.
+ */
+ public void setupContentDimensions(int count) {
+ mAllocatedContentSize = count;
+ calculateGridSize(count, mGridCountX, mGridCountY, mMaxCountX, mMaxCountY, mMaxItemsPerPage,
+ sTmpArray);
+ mGridCountX = sTmpArray[0];
+ mGridCountY = sTmpArray[1];
+
// Update grid size
for (int i = getPageCount() - 1; i >= 0; i--) {
getPageAt(i).setGridSize(mGridCountX, mGridCountY);
@@ -310,6 +328,8 @@ public class FolderPagedView extends PagedView {
int position = 0;
int newX, newY, rank;
+ FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(
+ Launcher.getLauncher(getContext()).getDeviceProfile().inv);
rank = 0;
for (int i = 0; i < itemCount; i++) {
View v = list.size() > i ? list.get(i) : null;
@@ -342,7 +362,7 @@ public class FolderPagedView extends PagedView {
currentPage.addViewToCellLayout(
v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
- if (rank < FolderIcon.NUM_ITEMS_IN_PREVIEW && v instanceof BubbleTextView) {
+ if (verifier.isItemInPreview(rank) && v instanceof BubbleTextView) {
((BubbleTextView) v).verifyHighRes();
}
}
@@ -396,12 +416,12 @@ public class FolderPagedView extends PagedView {
public int findNearestArea(int pixelX, int pixelY) {
int pageIndex = getNextPage();
CellLayout page = getPageAt(pageIndex);
- page.findNearestArea(pixelX, pixelY, 1, 1, sTempPosArray);
+ page.findNearestArea(pixelX, pixelY, 1, 1, sTmpArray);
if (mFolder.isLayoutRtl()) {
- sTempPosArray[0] = page.getCountX() - sTempPosArray[0] - 1;
+ sTmpArray[0] = page.getCountX() - sTmpArray[0] - 1;
}
return Math.min(mAllocatedContentSize - 1,
- pageIndex * mMaxItemsPerPage + sTempPosArray[1] * mGridCountX + sTempPosArray[0]);
+ pageIndex * mMaxItemsPerPage + sTmpArray[1] * mGridCountX + sTmpArray[0]);
}
public boolean isFull() {
diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
index 297203ae2..9c8c2efdb 100644
--- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
@@ -87,6 +87,11 @@ public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
}
@Override
+ public float getIconSize() {
+ return mBaselineIconSize;
+ }
+
+ @Override
public float scaleForItem(int index, int numItems) {
// Scale is determined by the position of the icon in the preview.
index = MAX_NUM_ITEMS_IN_PREVIEW - index - 1;
@@ -98,10 +103,4 @@ public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
public boolean clipToBackground() {
return false;
}
-
- @Override
- public List<View> getItemsToDisplay(Folder folder) {
- List<View> items = folder.getItemsInReadingOrder();
- return items.subList(0, Math.min(items.size(), MAX_NUM_ITEMS_IN_PREVIEW));
- }
}
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index bb136f7a3..492d85373 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -29,7 +29,7 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetHostView;
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
-import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon;
/**
@@ -138,7 +138,7 @@ public class DragPreviewProvider {
}
public final void generateDragOutline(Canvas canvas) {
- if (ProviderConfig.IS_DOGFOOD_BUILD && generatedDragOutline != null) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD && generatedDragOutline != null) {
throw new RuntimeException("Drag outline generated twice");
}
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index 8b207bb0c..60bbce406 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -85,10 +85,10 @@ public class DrawableFactory {
if (Utilities.isAtLeastO()) {
try {
// Try to load the path from Mask Icon
- Drawable maskIcon = context.getDrawable(R.drawable.mask_drawable_wrapper);
- maskIcon.setBounds(0, 0,
+ Drawable icon = context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper);
+ icon.setBounds(0, 0,
PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE);
- return (Path) maskIcon.getClass().getMethod("getIconMask").invoke(maskIcon);
+ return (Path) icon.getClass().getMethod("getIconMask").invoke(icon);
} catch (Exception e) {
Log.e(TAG, "Error loading mask icon", e);
}
diff --git a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
index c9873d9ea..b22182883 100644
--- a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
+++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
@@ -31,7 +31,7 @@ import android.util.SparseArray;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
-import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.config.FeatureFlags;
import java.nio.ByteBuffer;
@@ -86,7 +86,7 @@ public class HolographicOutlineHelper {
* bitmap.
*/
public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas) {
- if (ProviderConfig.IS_DOGFOOD_BUILD && srcDst.getConfig() != Bitmap.Config.ALPHA_8) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD && srcDst.getConfig() != Bitmap.Config.ALPHA_8) {
throw new RuntimeException("Outline blue is only supported on alpha bitmaps");
}
diff --git a/src/com/android/launcher3/graphics/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java
index 23c6a1230..cd7cf702e 100644
--- a/src/com/android/launcher3/graphics/IconPalette.java
+++ b/src/com/android/launcher3/graphics/IconPalette.java
@@ -19,11 +19,12 @@ package com.android.launcher3.graphics;
import android.app.Notification;
import android.content.Context;
import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
import android.support.v4.graphics.ColorUtils;
import android.util.Log;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.util.Themes;
/**
@@ -41,12 +42,16 @@ public class IconPalette {
public final int dominantColor;
public final int backgroundColor;
+ public final ColorMatrixColorFilter backgroundColorMatrixFilter;
public final int textColor;
public final int secondaryColor;
private IconPalette(int color) {
dominantColor = color;
backgroundColor = getMutedColor(dominantColor);
+ ColorMatrix backgroundColorMatrix = new ColorMatrix();
+ Themes.setColorScaleOnMatrix(backgroundColor, backgroundColorMatrix);
+ backgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
textColor = getTextColorForBackground(backgroundColor);
secondaryColor = getLowContrastColor(backgroundColor);
}
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 1a50dfe14..ef54661d3 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -40,7 +40,6 @@ import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
@@ -166,7 +165,7 @@ public class LauncherIcons {
* @param scale the scale to apply before drawing {@param icon} on the canvas
*/
public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
- icon = wrapToMaskableIconDrawable(context, icon);
+ icon = wrapToAdaptiveIconDrawable(context, icon);
synchronized (sCanvas) {
final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
@@ -201,7 +200,7 @@ public class LauncherIcons {
int textureWidth = iconBitmapSize;
int textureHeight = iconBitmapSize;
- final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
+ Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
Bitmap.Config.ARGB_8888);
final Canvas canvas = sCanvas;
canvas.setBitmap(bitmap);
@@ -218,29 +217,39 @@ public class LauncherIcons {
icon.setBounds(sOldBounds);
canvas.setBitmap(null);
+ if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.isAtLeastO()) {
+ try {
+ Class clazz = Class.forName("android.graphics.drawable.AdaptiveIconDrawable");
+ if (clazz.isAssignableFrom(icon.getClass())) {
+ bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap);
+ }
+ } catch (Exception e) {
+ // do nothing
+ }
+ }
return bitmap;
}
}
/**
- * If the platform is running O but the app is not providing MaskableIconDrawable, then
+ * If the platform is running O but the app is not providing AdaptiveIconDrawable, then
* shrink the legacy icon and set it as foreground. Use color drawable as background to
- * create MaskableIconDrawable.
+ * create AdaptiveIconDrawable.
*/
- static Drawable wrapToMaskableIconDrawable(Context context, Drawable drawable) {
+ static Drawable wrapToAdaptiveIconDrawable(Context context, Drawable drawable) {
if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.isAtLeastO())) {
return drawable;
}
try {
- Class clazz = Class.forName("android.graphics.drawable.MaskableIconDrawable");
+ Class clazz = Class.forName("android.graphics.drawable.AdaptiveIconDrawable");
if (!clazz.isAssignableFrom(drawable.getClass())) {
- Drawable maskWrapper =
- context.getDrawable(R.drawable.mask_drawable_wrapper).mutate();
- ((FixedScaleDrawable) clazz.getMethod("getForeground").invoke(maskWrapper))
+ Drawable iconWrapper =
+ context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
+ ((FixedScaleDrawable) clazz.getMethod("getForeground").invoke(iconWrapper))
.setDrawable(drawable);
- return maskWrapper;
+ return iconWrapper;
}
} catch (Exception e) {
return drawable;
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 3514a37c4..22ce0981d 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -217,6 +217,9 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
if (Float.compare(finalProgress, mInternalStateProgress) == 0) {
return;
}
+ if (finalProgress < mInternalStateProgress) {
+ shouldAnimate = false;
+ }
if (!shouldAnimate || mRanFinishAnimation) {
setInternalProgress(finalProgress);
} else {
diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/graphics/ShadowGenerator.java
index 31276ecc0..6c603c971 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/graphics/ShadowGenerator.java
@@ -83,6 +83,38 @@ public class ShadowGenerator {
return result;
}
+ public static Bitmap createCircleWithShadow(int circleColor, int diameter) {
+
+ float shadowRadius = diameter * 1f / 32;
+ float shadowYOffset = diameter * 1f / 16;
+
+ int radius = diameter / 2;
+
+ Canvas canvas = new Canvas();
+ Paint blurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ blurPaint.setMaskFilter(new BlurMaskFilter(shadowRadius, Blur.NORMAL));
+
+ int center = Math.round(radius + shadowRadius + shadowYOffset);
+ int size = center * 2;
+ Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
+ canvas.setBitmap(result);
+
+ // Draw ambient shadow, center aligned within size
+ blurPaint.setAlpha(AMBIENT_SHADOW_ALPHA);
+ canvas.drawCircle(center, center, radius, blurPaint);
+
+ // Draw key shadow, bottom aligned within size
+ blurPaint.setAlpha(KEY_SHADOW_ALPHA);
+ canvas.drawCircle(center, center + shadowYOffset, radius, blurPaint);
+
+ // Draw the circle
+ Paint drawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ drawPaint.setColor(circleColor);
+ canvas.drawCircle(center, center, radius, drawPaint);
+
+ return result;
+ }
+
public static ShadowGenerator getInstance(Context context) {
Preconditions.assertNonUiThread();
synchronized (LOCK) {
diff --git a/src/com/android/launcher3/logging/DumpTargetWrapper.java b/src/com/android/launcher3/logging/DumpTargetWrapper.java
new file mode 100644
index 000000000..2646a2242
--- /dev/null
+++ b/src/com/android/launcher3/logging/DumpTargetWrapper.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.logging;
+
+import android.os.Process;
+import android.text.TextUtils;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.nano.LauncherDumpProto;
+import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
+import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
+import com.android.launcher3.model.nano.LauncherDumpProto.ItemType;
+import com.android.launcher3.model.nano.LauncherDumpProto.UserType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class can be used when proto definition doesn't support nesting.
+ */
+public class DumpTargetWrapper {
+ DumpTarget node;
+ ArrayList<DumpTargetWrapper> children;
+
+ public DumpTargetWrapper() {
+ children = new ArrayList<>();
+ }
+
+ public DumpTargetWrapper(DumpTarget t) {
+ this();
+ node = t;
+ }
+
+ public DumpTargetWrapper(int containerType, int id) {
+ this();
+ node = newContainerTarget(containerType, id);
+ }
+
+ public DumpTargetWrapper(ItemInfo info) {
+ this();
+ node = newItemTarget(info);
+ }
+
+ public DumpTarget getDumpTarget() {
+ return node;
+ }
+
+ public void add(DumpTargetWrapper child) {
+ children.add(child);
+ }
+
+ public List<DumpTarget> getFlattenedList() {
+ ArrayList<DumpTarget> list = new ArrayList<>();
+ list.add(node);
+ if (!children.isEmpty()) {
+ for(DumpTargetWrapper t: children) {
+ list.addAll(t.getFlattenedList());
+ }
+ list.add(node); // add a delimiter empty object
+ }
+ return list;
+ }
+ public DumpTarget newItemTarget(ItemInfo info) {
+ DumpTarget dt = new DumpTarget();
+ dt.type = DumpTarget.Type.ITEM;
+
+ switch (info.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ dt.itemType = ItemType.APP_ICON;
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ dt.itemType = ItemType.UNKNOWN_ITEMTYPE;
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ dt.itemType = ItemType.WIDGET;
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+ dt.itemType = ItemType.SHORTCUT;
+ break;
+ }
+ return dt;
+ }
+
+ public DumpTarget newContainerTarget(int type, int id) {
+ DumpTarget dt = new DumpTarget();
+ dt.type = DumpTarget.Type.CONTAINER;
+ dt.containerType = type;
+ dt.pageId = id;
+ return dt;
+ }
+
+ public static String getDumpTargetStr(DumpTarget t) {
+ if (t == null){
+ return "";
+ }
+ switch (t.type) {
+ case LauncherDumpProto.DumpTarget.Type.ITEM:
+ return getItemStr(t);
+ case LauncherDumpProto.DumpTarget.Type.CONTAINER:
+ String str = LoggerUtils.getFieldName(t.containerType, ContainerType.class);
+ if (t.containerType == ContainerType.WORKSPACE) {
+ str += " id=" + t.pageId;
+ } else if (t.containerType == ContainerType.FOLDER) {
+ str += " grid(" + t.gridX + "," + t.gridY+ ")";
+ }
+ return str;
+ default:
+ return "UNKNOWN TARGET TYPE";
+ }
+ }
+
+ private static String getItemStr(DumpTarget t) {
+ String typeStr = LoggerUtils.getFieldName(t.itemType, ItemType.class);
+ if (!TextUtils.isEmpty(t.packageName)) {
+ typeStr += ", package=" + t.packageName;
+ }
+ if (!TextUtils.isEmpty(t.component)) {
+ typeStr += ", component=" + t.component;
+ }
+ return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
+ + "), pageIdx=" + t.pageId + " user=" + t.userType;
+ }
+
+ public DumpTarget writeToDumpTarget(ItemInfo info) {
+ node.component = info.getTargetComponent() == null? "":
+ info.getTargetComponent().flattenToString();
+ node.packageName = info.getIntent() == null? "": info.getIntent().getPackage();
+ node.gridX = info.cellX;
+ node.gridY = info.cellY;
+ node.spanX = info.spanX;
+ node.spanY = info.spanY;
+ node.userType = (info.user.equals(Process.myUserHandle()))? UserType.DEFAULT : UserType.WORK;
+ return node;
+ }
+}
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index ffb41b76b..4c83e9ac2 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -6,9 +6,8 @@ import android.os.Message;
import android.util.Log;
import android.util.Pair;
-import com.android.launcher3.LauncherModel;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.config.FeatureFlags;
import java.io.BufferedReader;
import java.io.File;
@@ -40,7 +39,7 @@ public final class FileLog {
private static File sLogsDirectory = null;
public static void setDir(File logsDir) {
- if (ProviderConfig.IS_DOGFOOD_BUILD || Utilities.IS_DEBUG_DEVICE) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD || Utilities.IS_DEBUG_DEVICE) {
synchronized (DATE_FORMAT) {
// If the target directory changes, stop any active thread.
if (sHandler != null && !logsDir.equals(sLogsDirectory)) {
@@ -77,7 +76,7 @@ public final class FileLog {
}
public static void print(String tag, String msg, Exception e) {
- if (!ProviderConfig.IS_DOGFOOD_BUILD) {
+ if (!FeatureFlags.IS_DOGFOOD_BUILD) {
return;
}
String out = String.format("%s %s %s", DATE_FORMAT.format(new Date()), tag, msg);
@@ -103,7 +102,7 @@ public final class FileLog {
* @param out if not null, all the persisted logs are copied to the writer.
*/
public static void flushAll(PrintWriter out) throws InterruptedException {
- if (!ProviderConfig.IS_DOGFOOD_BUILD) {
+ if (!FeatureFlags.IS_DOGFOOD_BUILD) {
return;
}
CountDownLatch latch = new CountDownLatch(1);
@@ -136,7 +135,7 @@ public final class FileLog {
@Override
public boolean handleMessage(Message msg) {
- if (sLogsDirectory == null || !ProviderConfig.IS_DOGFOOD_BUILD) {
+ if (sLogsDirectory == null || !FeatureFlags.IS_DOGFOOD_BUILD) {
return true;
}
switch (msg.what) {
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index c13e8b336..499fdc7d3 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -1,5 +1,21 @@
+/*
+ * Copyright (C) 2016 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.logging;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.SparseArray;
import android.view.View;
@@ -27,7 +43,7 @@ public class LoggerUtils {
private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>();
private static final String UNKNOWN = "UNKNOWN";
- private static String getFieldName(int value, Class c) {
+ public static String getFieldName(int value, Class c) {
SparseArray<String> cache;
synchronized (sNameCache) {
cache = sNameCache.get(c);
@@ -68,8 +84,13 @@ public class LoggerUtils {
case Target.Type.CONTROL:
return getFieldName(t.controlType, ControlType.class);
case Target.Type.CONTAINER:
- return getFieldName(t.containerType, ContainerType.class)
- + " id=" + t.pageIndex;
+ String str = getFieldName(t.containerType, ContainerType.class);
+ if (t.containerType == ContainerType.WORKSPACE) {
+ str += " id=" + t.pageIndex;
+ } else if (t.containerType == ContainerType.FOLDER) {
+ str += " grid(" + t.gridX + "," + t.gridY+ ")";
+ }
+ return str;
default:
return "UNKNOWN TARGET TYPE";
}
@@ -86,10 +107,8 @@ public class LoggerUtils {
if (t.intentHash != 0) {
typeStr += ", intentHash=" + t.intentHash;
}
- if (t.spanX != 0) {
- typeStr += ", spanX=" + t.spanX;
- }
- return typeStr + ", grid=(" + t.gridX + "," + t.gridY + "), id=" + t.pageIndex;
+ return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
+ + "), pageIdx=" + t.pageIndex;
}
public static Target newItemTarget(View v) {
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 5f45c6176..04ca24741 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -18,6 +18,7 @@ package com.android.launcher3.logging;
import android.app.PendingIntent;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;
import android.util.Log;
@@ -26,9 +27,9 @@ import android.view.ViewParent;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.ProviderConfig;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
@@ -59,7 +60,20 @@ public class UserEventDispatcher {
private static final String TAG = "UserEvent";
private static final boolean IS_VERBOSE =
- ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT);
+ FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT);
+
+ private static UserEventDispatcher sInstance;
+ private static final Object LOCK = new Object();
+
+ public static UserEventDispatcher get(Context context) {
+ synchronized (LOCK) {
+ if (sInstance == null) {
+ sInstance = Utilities.getOverrideObject(UserEventDispatcher.class,
+ context.getApplicationContext(), R.string.user_event_dispatcher_class);
+ }
+ return sInstance;
+ }
+ }
/**
* Implemented by containers to provide a container source for a given child.
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 6b64087a2..930c854c3 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -24,19 +24,25 @@ import android.util.MutableInt;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.DumpTargetWrapper;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.model.nano.LauncherDumpProto;
+import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
+import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.MultiHashMap;
+import com.google.protobuf.nano.MessageNano;
import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -102,21 +108,31 @@ public class BgDataModel {
deepShortcutMap.clear();
}
- // TODO: current dump is very cryptic and hard to understand. Make it more legible.
- public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
+ String[] args) {
+ if (args.length > 0 && TextUtils.equals(args[0], "--proto")) {
+ dumpProto(prefix, fd, writer, args);
+ return;
+ }
writer.println(prefix + "Data Model:");
+ writer.print(prefix + " ---- workspace screens: ");
for (int i = 0; i < workspaceScreens.size(); i++) {
- writer.println(prefix + "\tIndex of workspaceScreens:" + workspaceScreens.get(i).toString());
+ writer.print(" " + workspaceScreens.get(i).toString());
}
+ writer.println();
+ writer.println(prefix + " ---- workspace items ");
for (int i = 0; i < workspaceItems.size(); i++) {
writer.println(prefix + '\t' + workspaceItems.get(i).toString());
}
+ writer.println(prefix + " ---- appwidget items ");
for (int i = 0; i < appWidgets.size(); i++) {
writer.println(prefix + '\t' + appWidgets.get(i).toString());
}
+ writer.println(prefix + " ---- folder items ");
for (int i = 0; i< folders.size(); i++) {
writer.println(prefix + '\t' + folders.valueAt(i).toString());
}
+ writer.println(prefix + " ---- items id map ");
for (int i = 0; i< itemsIdMap.size(); i++) {
writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
}
@@ -133,6 +149,88 @@ public class BgDataModel {
}
}
+ private synchronized void dumpProto(String prefix, FileDescriptor fd, PrintWriter writer,
+ String[] args) {
+
+ // Add top parent nodes. (L1)
+ DumpTargetWrapper hotseat = new DumpTargetWrapper(ContainerType.HOTSEAT, 0);
+ LongArrayMap<DumpTargetWrapper> workspaces = new LongArrayMap<>();
+ for (int i = 0; i < workspaceScreens.size(); i++) {
+ workspaces.put(new Long(workspaceScreens.get(i)),
+ new DumpTargetWrapper(ContainerType.WORKSPACE, i));
+ }
+ DumpTargetWrapper dtw;
+ // Add non leaf / non top nodes (L2)
+ for (int i = 0; i < folders.size(); i++) {
+ FolderInfo fInfo = folders.valueAt(i);
+ dtw = new DumpTargetWrapper(ContainerType.FOLDER, folders.size());
+ dtw.writeToDumpTarget(fInfo);
+ for(ShortcutInfo sInfo: fInfo.contents) {
+ DumpTargetWrapper child = new DumpTargetWrapper(sInfo);
+ child.writeToDumpTarget(sInfo);
+ dtw.add(child);
+ }
+ if (fInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ hotseat.add(dtw);
+ } else if (fInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ workspaces.get(new Long(fInfo.screenId)).add(dtw);
+ }
+ }
+ // Add leaf nodes (L3): *Info
+ for (int i = 0; i < workspaceItems.size(); i++) {
+ ItemInfo info = workspaceItems.get(i);
+ if (info instanceof FolderInfo) {
+ continue;
+ }
+ dtw = new DumpTargetWrapper(info);
+ dtw.writeToDumpTarget(info);
+ if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ hotseat.add(dtw);
+ } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ workspaces.get(new Long(info.screenId)).add(dtw);
+ }
+ }
+ for (int i = 0; i < appWidgets.size(); i++) {
+ ItemInfo info = appWidgets.get(i);
+ dtw = new DumpTargetWrapper(info);
+ dtw.writeToDumpTarget(info);
+ if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ hotseat.add(dtw);
+ } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ workspaces.get(new Long(info.screenId)).add(dtw);
+ }
+ }
+
+
+ // Traverse target wrapper
+ ArrayList<DumpTarget> targetList = new ArrayList<>();
+ targetList.addAll(hotseat.getFlattenedList());
+ for (int i = 0; i < workspaces.size(); i++) {
+ targetList.addAll(workspaces.valueAt(i).getFlattenedList());
+ }
+
+ if (args.length > 1 && TextUtils.equals(args[1], "--debug")) {
+ for (int i = 0; i < targetList.size(); i++) {
+ writer.println(prefix + DumpTargetWrapper.getDumpTargetStr(targetList.get(i)));
+ }
+ return;
+ } else {
+ LauncherDumpProto.LauncherImpression proto = new LauncherDumpProto.LauncherImpression();
+ proto.targets = new DumpTarget[targetList.size()];
+ for (int i = 0; i < targetList.size(); i++) {
+ proto.targets[i] = targetList.get(i);
+ }
+ FileOutputStream fos = new FileOutputStream(fd);
+ try {
+
+ fos.write(MessageNano.toByteArray(proto));
+ Log.d(TAG, MessageNano.toByteArray(proto).length + "Bytes");
+ } catch (IOException e) {
+ Log.e(TAG, "Exception writing dumpsys --proto", e);
+ }
+ }
+ }
+
public synchronized void removeItem(Context context, ItemInfo... items) {
removeItem(context, Arrays.asList(items));
}
@@ -142,7 +240,7 @@ public class BgDataModel {
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
folders.remove(item.id);
- if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD) {
for (ItemInfo info : itemsIdMap) {
if (info.container == item.id) {
// We are deleting a folder which still contains items that
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 4931dca14..032ed780d 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -34,7 +34,7 @@ import com.android.launcher3.LauncherSettings.Settings;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.LooperExecuter;
+import com.android.launcher3.util.LooperExecutor;
import java.util.ArrayList;
import java.util.Arrays;
@@ -55,7 +55,7 @@ public class ModelWriter {
public ModelWriter(Context context, BgDataModel dataModel, boolean hasVerticalHotseat) {
mContext = context;
mBgDataModel = dataModel;
- mWorkerExecutor = new LooperExecuter(LauncherModel.getWorkerLooper());
+ mWorkerExecutor = new LooperExecutor(LauncherModel.getWorkerLooper());
mHasVerticalHotseat = hasVerticalHotseat;
}
diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
index 7c9836282..278669bdb 100644
--- a/src/com/android/launcher3/model/SdCardAvailableReceiver.java
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
@@ -63,7 +63,7 @@ public class SdCardAvailableReceiver extends BroadcastReceiver {
for (String pkg : new HashSet<>(entry.getValue())) {
if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
- if (pmHelper.isAppOnSdcard(pkg)) {
+ if (pmHelper.isAppOnSdcard(pkg, user)) {
packagesUnavailable.add(pkg);
} else {
packagesRemoved.add(pkg);
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 95c54f767..d2adbde23 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -3,9 +3,7 @@ package com.android.launcher3.model;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
@@ -19,8 +17,7 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.ShortcutConfigActivityInfo;
-import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.Preconditions;
@@ -79,7 +76,7 @@ public class WidgetsModel {
}
setWidgetsAndShortcuts(widgetsAndShortcuts, context);
} catch (Exception e) {
- if (!ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isBinderSizeError(e)) {
+ if (!FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isBinderSizeError(e)) {
// the returned value may be incomplete and will not be refreshed until the next
// time Launcher starts.
// TODO: after figuring out a repro step, introduce a dirty bit to check when
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index 57ec5d17c..2e803417d 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -21,19 +21,22 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
-import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
-import android.widget.TextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.anim.PropertyResetListener;
import com.android.launcher3.popup.PopupContainerWithArrow;
import java.util.ArrayList;
@@ -41,10 +44,10 @@ import java.util.Iterator;
import java.util.List;
/**
- * A {@link LinearLayout} that contains only icons of notifications.
- * If there are more than {@link #MAX_FOOTER_NOTIFICATIONS} icons, we add a "+x" overflow.
+ * A {@link FrameLayout} that contains only icons of notifications.
+ * If there are more than {@link #MAX_FOOTER_NOTIFICATIONS} icons, we add a "..." overflow.
*/
-public class NotificationFooterLayout extends LinearLayout {
+public class NotificationFooterLayout extends FrameLayout {
public interface IconAnimationEndListener {
void onIconAnimationEnd(NotificationInfo animatedNotification);
@@ -56,11 +59,12 @@ public class NotificationFooterLayout extends LinearLayout {
private final List<NotificationInfo> mNotifications = new ArrayList<>();
private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
+ private final boolean mRtl;
- LinearLayout.LayoutParams mIconLayoutParams;
+ FrameLayout.LayoutParams mIconLayoutParams;
+ private View mOverflowEllipsis;
private LinearLayout mIconRow;
private int mBackgroundColor;
- private int mTextColor;
public NotificationFooterLayout(Context context) {
this(context, null, 0);
@@ -73,26 +77,29 @@ public class NotificationFooterLayout extends LinearLayout {
public NotificationFooterLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- int size = getResources().getDimensionPixelSize(
- R.dimen.notification_footer_icon_size);
- int padding = getResources().getDimensionPixelSize(
- R.dimen.deep_shortcut_drawable_padding);
- mIconLayoutParams = new LayoutParams(size, size);
- mIconLayoutParams.setMarginStart(padding);
+ Resources res = getResources();
+ mRtl = Utilities.isRtl(res);
+
+ int iconSize = res.getDimensionPixelSize(R.dimen.notification_footer_icon_size);
+ mIconLayoutParams = new LayoutParams(iconSize, iconSize);
mIconLayoutParams.gravity = Gravity.CENTER_VERTICAL;
+ // Compute margin start for each icon such that the icons between the first one
+ // and the ellipsis are evenly spaced out.
+ int paddingEnd = res.getDimensionPixelSize(R.dimen.notification_footer_icon_row_padding);
+ int ellipsisSpace = res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_offset)
+ + res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_size);
+ int footerWidth = res.getDimensionPixelSize(R.dimen.bg_popup_item_width);
+ int availableIconRowSpace = footerWidth - paddingEnd - ellipsisSpace
+ - iconSize * MAX_FOOTER_NOTIFICATIONS;
+ mIconLayoutParams.setMarginStart(availableIconRowSpace / MAX_FOOTER_NOTIFICATIONS);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ mOverflowEllipsis = findViewById(R.id.overflow);
mIconRow = (LinearLayout) findViewById(R.id.icon_row);
- }
-
- public void applyColors(IconPalette iconPalette) {
- mBackgroundColor = iconPalette.backgroundColor;
- setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
- findViewById(R.id.divider).setBackgroundColor(iconPalette.secondaryColor);
- mTextColor = iconPalette.textColor;
+ mBackgroundColor = ((ColorDrawable) getBackground()).getColor();
}
/**
@@ -116,41 +123,32 @@ public class NotificationFooterLayout extends LinearLayout {
for (int i = 0; i < mNotifications.size(); i++) {
NotificationInfo info = mNotifications.get(i);
- addNotificationIconForInfo(info, false /* fromOverflow */);
+ addNotificationIconForInfo(info);
}
+ updateOverflowEllipsisVisibility();
+ }
- if (!mOverflowNotifications.isEmpty()) {
- TextView overflowText = new TextView(getContext());
- overflowText.setTextColor(mTextColor);
- updateOverflowText(overflowText);
- mIconRow.addView(overflowText, mIconLayoutParams);
- }
+ private void updateOverflowEllipsisVisibility() {
+ mOverflowEllipsis.setVisibility(mOverflowNotifications.isEmpty() ? GONE : VISIBLE);
}
- private void addNotificationIconForInfo(NotificationInfo info, boolean fromOverflow) {
+ /**
+ * Creates an icon for the given NotificationInfo, and adds it to the icon row.
+ * @return the icon view that was added
+ */
+ private View addNotificationIconForInfo(NotificationInfo info) {
View icon = new View(getContext());
icon.setBackground(info.getIconForBackground(getContext(), mBackgroundColor));
icon.setOnClickListener(info);
- int addIndex = mIconRow.getChildCount();
- if (fromOverflow) {
- // Add the notification before the overflow view.
- addIndex--;
- icon.setAlpha(0);
- icon.animate().alpha(1);
- }
icon.setTag(info);
- mIconRow.addView(icon, addIndex, mIconLayoutParams);
- }
-
- private void updateOverflowText(TextView overflowTextView) {
- overflowTextView.setText(getResources().getString(R.string.deep_notifications_overflow,
- mOverflowNotifications.size()));
+ mIconRow.addView(icon, 0, mIconLayoutParams);
+ return icon;
}
public void animateFirstNotificationTo(Rect toBounds,
final IconAnimationEndListener callback) {
AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
- final View firstNotification = mIconRow.getChildAt(0);
+ final View firstNotification = mIconRow.getChildAt(mIconRow.getChildCount() - 1);
Rect fromBounds = sTempRect;
firstNotification.getGlobalVisibleRect(fromBounds);
@@ -162,29 +160,54 @@ public class NotificationFooterLayout extends LinearLayout {
@Override
public void onAnimationEnd(Animator animation) {
callback.onIconAnimationEnd((NotificationInfo) firstNotification.getTag());
+ removeViewFromIconRow(firstNotification);
}
});
animation.play(moveAndScaleIcon);
// Shift all notifications (not the overflow) over to fill the gap.
int gapWidth = mIconLayoutParams.width + mIconLayoutParams.getMarginStart();
- int numIcons = mIconRow.getChildCount()
- - (mOverflowNotifications.isEmpty() ? 0 : 1);
- for (int i = 1; i < numIcons; i++) {
+ if (mRtl) {
+ gapWidth = -gapWidth;
+ }
+ if (!mOverflowNotifications.isEmpty()) {
+ NotificationInfo notification = mOverflowNotifications.remove(0);
+ mNotifications.add(notification);
+ View iconFromOverflow = addNotificationIconForInfo(notification);
+ animation.play(ObjectAnimator.ofFloat(iconFromOverflow, ALPHA, 0, 1));
+ }
+ int numIcons = mIconRow.getChildCount() - 1; // All children besides the one leaving.
+ // We have to reset the translation X to 0 when the new main notification
+ // is removed from the footer.
+ PropertyResetListener<View, Float> propertyResetListener
+ = new PropertyResetListener<>(TRANSLATION_X, 0f);
+ for (int i = 0; i < numIcons; i++) {
final View child = mIconRow.getChildAt(i);
- Animator shiftChild = ObjectAnimator.ofFloat(child, TRANSLATION_X, -gapWidth);
- shiftChild.addListener(new AnimatorListenerAdapter() {
+ Animator shiftChild = ObjectAnimator.ofFloat(child, TRANSLATION_X, gapWidth);
+ shiftChild.addListener(propertyResetListener);
+ animation.play(shiftChild);
+ }
+ animation.start();
+ }
+
+ private void removeViewFromIconRow(View child) {
+ mIconRow.removeView(child);
+ mNotifications.remove((NotificationInfo) child.getTag());
+ updateOverflowEllipsisVisibility();
+ if (mIconRow.getChildCount() == 0) {
+ // There are no more icons in the footer, so hide it.
+ PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(
+ Launcher.getLauncher(getContext()));
+ Animator collapseFooter = popup.reduceNotificationViewHeight(getHeight(),
+ getResources().getInteger(R.integer.config_removeNotificationViewDuration));
+ collapseFooter.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- // We have to set the translation X to 0 when the new main notification
- // is removed from the footer.
- // TODO: remove it here instead of expecting trimNotifications to do so.
- child.setTranslationX(0);
+ ((ViewGroup) getParent()).removeView(NotificationFooterLayout.this);
}
});
- animation.play(shiftChild);
+ collapseFooter.start();
}
- animation.start();
}
public void trimNotifications(List<String> notifications) {
@@ -197,43 +220,12 @@ public class NotificationFooterLayout extends LinearLayout {
overflowIterator.remove();
}
}
- TextView overflowView = null;
for (int i = mIconRow.getChildCount() - 1; i >= 0; i--) {
View child = mIconRow.getChildAt(i);
- if (child instanceof TextView) {
- overflowView = (TextView) child;
- } else {
- NotificationInfo childInfo = (NotificationInfo) child.getTag();
- if (!notifications.contains(childInfo.notificationKey)) {
- mIconRow.removeView(child);
- mNotifications.remove(childInfo);
- if (!mOverflowNotifications.isEmpty()) {
- NotificationInfo notification = mOverflowNotifications.remove(0);
- mNotifications.add(notification);
- addNotificationIconForInfo(notification, true /* fromOverflow */);
- }
- }
- }
- }
- if (overflowView != null) {
- if (mOverflowNotifications.isEmpty()) {
- mIconRow.removeView(overflowView);
- } else {
- updateOverflowText(overflowView);
+ NotificationInfo childInfo = (NotificationInfo) child.getTag();
+ if (!notifications.contains(childInfo.notificationKey)) {
+ removeViewFromIconRow(child);
}
}
- if (mIconRow.getChildCount() == 0) {
- // There are no more icons in the secondary view, so hide it.
- PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(
- Launcher.getLauncher(getContext()));
- int newHeight = getResources().getDimensionPixelSize(
- R.dimen.notification_footer_collapsed_height);
- AnimatorSet collapseSecondary = LauncherAnimUtils.createAnimatorSet();
- collapseSecondary.play(popup.animateTranslationYBy(getHeight() - newHeight,
- getResources().getInteger(R.integer.config_removeNotificationViewDuration)));
- collapseSecondary.play(LauncherAnimUtils.animateViewHeight(
- this, getHeight(), newHeight));
- collapseSecondary.start();
- }
}
}
diff --git a/src/com/android/launcher3/notification/NotificationHeaderView.java b/src/com/android/launcher3/notification/NotificationHeaderView.java
new file mode 100644
index 000000000..e70b48949
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationHeaderView.java
@@ -0,0 +1,59 @@
+/*
+ * 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.notification;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+
+/**
+ * A {@link LinearLayout} that contains two text views: one for the notification count
+ * and one just to say "Notification" or "Notifications"
+ */
+public class NotificationHeaderView extends LinearLayout {
+
+ private TextView mNotificationCount;
+ private TextView mNotificationText;
+
+ public NotificationHeaderView(Context context) {
+ this(context, null, 0);
+ }
+
+ public NotificationHeaderView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationHeaderView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mNotificationCount = (TextView) findViewById(R.id.notification_count);
+ mNotificationText = (TextView) findViewById(R.id.notification_text);
+ }
+
+ public void update(int notificationCount) {
+ mNotificationCount.setText(String.valueOf(notificationCount));
+ mNotificationText.setText(getResources().getQuantityString(
+ R.plurals.notifications_header, notificationCount));
+ }
+}
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index c9b3940b8..a34074271 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -18,30 +18,23 @@ package com.android.launcher3.notification;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
-import android.view.animation.LinearInterpolator;
+import android.view.ViewGroup;
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.graphics.IconPalette;
+import com.android.launcher3.anim.PillHeightRevealOutlineProvider;
import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
import com.android.launcher3.popup.PopupItemView;
import com.android.launcher3.userevent.nano.LauncherLogProto;
-import java.util.ArrayList;
import java.util.List;
-import static com.android.launcher3.LauncherAnimUtils.animateViewHeight;
-
/**
* A {@link FrameLayout} that contains a header, main view and a footer.
* The main view contains the icon and text (title + subtext) of the first notification.
@@ -52,8 +45,7 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
private static final Rect sTempRect = new Rect();
- private TextView mHeader;
- private View mDivider;
+ private NotificationHeaderView mHeader;
private NotificationMainView mMainView;
private NotificationFooterLayout mFooter;
private SwipeHelper mSwipeHelper;
@@ -74,11 +66,35 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mHeader = (TextView) findViewById(R.id.header);
- mDivider = findViewById(R.id.divider);
+ mHeader = (NotificationHeaderView) findViewById(R.id.header);
mMainView = (NotificationMainView) findViewById(R.id.main_view);
mFooter = (NotificationFooterLayout) findViewById(R.id.footer);
mSwipeHelper = new SwipeHelper(SwipeHelper.X, mMainView, getContext());
+ mSwipeHelper.setDisableHardwareLayers(true);
+ }
+
+ public Animator animateHeightRemoval(int heightToRemove) {
+ final int newHeight = getHeight() - heightToRemove;
+ Animator heightAnimator = new PillHeightRevealOutlineProvider(mPillRect,
+ getBackgroundRadius(), newHeight).createRevealAnimator(this, true /* isReversed */);
+ heightAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (newHeight > 0) {
+ measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
+ initializeBackgroundClipping(true /* force */);
+ invalidate();
+ } else {
+ ((ViewGroup) getParent()).removeView(NotificationItemView.this);
+ }
+ }
+ });
+ return heightAnimator;
+ }
+
+ public void updateHeader(int notificationCount) {
+ mHeader.update(notificationCount);
}
@Override
@@ -100,13 +116,6 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
return mSwipeHelper.onTouchEvent(ev) || super.onTouchEvent(ev);
}
- @Override
- protected ColorStateList getAttachedArrowColor() {
- // This NotificationView itself has a different color that is only
- // revealed when dismissing notifications.
- return mFooter.getBackgroundTintList();
- }
-
public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
if (notificationInfos.isEmpty()) {
return;
@@ -121,15 +130,6 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
mFooter.commitNotificationInfos();
}
- public void applyColors(IconPalette iconPalette) {
- setBackgroundTintList(ColorStateList.valueOf(iconPalette.secondaryColor));
- mHeader.setBackgroundTintList(ColorStateList.valueOf(iconPalette.backgroundColor));
- mHeader.setTextColor(ColorStateList.valueOf(iconPalette.textColor));
- mDivider.setBackgroundColor(iconPalette.secondaryColor);
- mMainView.applyColors(iconPalette);
- mFooter.applyColors(iconPalette);
- }
-
public void trimNotifications(final List<String> notificationKeys) {
boolean dismissedMainNotification = !notificationKeys.contains(
mMainView.getNotificationInfo().notificationKey);
@@ -145,12 +145,6 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
public void onIconAnimationEnd(NotificationInfo newMainNotification) {
if (newMainNotification != null) {
mMainView.applyNotificationInfo(newMainNotification, mIconView, true);
- // Remove the animated notification from the footer by calling trim
- // TODO: Remove the notification in NotificationFooterLayout directly
- // instead of relying on this hack.
- List<String> footerNotificationKeys = new ArrayList<>(notificationKeys);
- footerNotificationKeys.remove(newMainNotification.notificationKey);
- mFooter.trimNotifications(footerNotificationKeys);
mMainView.setVisibility(VISIBLE);
}
mAnimatingNextIcon = false;
@@ -161,27 +155,6 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
}
}
- public Animator createRemovalAnimation(int fullDuration) {
- AnimatorSet animation = new AnimatorSet();
- int mainHeight = mMainView.getMeasuredHeight();
- Animator removeMainView = animateViewHeight(mMainView, mainHeight, 0);
- removeMainView.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- // Remove the remaining views but take on their color instead of the darker one.
- setBackgroundTintList(mHeader.getBackgroundTintList());
- removeAllViews();
- }
- });
- Animator removeRest = LauncherAnimUtils.animateViewHeight(this, getHeight() - mainHeight, 0);
- removeMainView.setDuration(fullDuration / 2);
- removeRest.setDuration(fullDuration / 2);
- removeMainView.setInterpolator(new LinearInterpolator());
- removeRest.setInterpolator(new LinearInterpolator());
- animation.playSequentially(removeMainView, removeRest);
- return animation;
- }
-
@Override
public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
LauncherLogProto.Target targetParent) {
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 5c16176f5..d9f7d7634 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -26,6 +26,7 @@ import android.support.annotation.Nullable;
import android.support.v4.util.Pair;
import com.android.launcher3.LauncherModel;
+import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.PackageUserKey;
@@ -40,7 +41,7 @@ import java.util.Set;
* A {@link NotificationListenerService} that sends updates to its
* {@link NotificationsChangedListener} when notifications are posted or canceled,
* as well and when this service first connects. An instance of NotificationListener,
- * and its methods for getting notifications, can be obtained via {@link #getInstance()}.
+ * and its methods for getting notifications, can be obtained via {@link #getInstanceIfConnected()}.
*/
public class NotificationListener extends NotificationListenerService {
@@ -50,10 +51,13 @@ public class NotificationListener extends NotificationListenerService {
private static NotificationListener sNotificationListenerInstance = null;
private static NotificationsChangedListener sNotificationsChangedListener;
+ private static boolean sIsConnected;
private final Handler mWorkerHandler;
private final Handler mUiHandler;
+ private Ranking mTempRanking = new Ranking();
+
private Handler.Callback mWorkerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
@@ -65,8 +69,9 @@ public class NotificationListener extends NotificationListenerService {
mUiHandler.obtainMessage(message.what, message.obj).sendToTarget();
break;
case MSG_NOTIFICATION_FULL_REFRESH:
- final List<StatusBarNotification> activeNotifications
- = filterNotifications(getActiveNotifications());
+ final List<StatusBarNotification> activeNotifications = sIsConnected
+ ? filterNotifications(getActiveNotifications())
+ : new ArrayList<StatusBarNotification>();
mUiHandler.obtainMessage(message.what, activeNotifications).sendToTarget();
break;
}
@@ -107,10 +112,11 @@ public class NotificationListener extends NotificationListenerService {
super();
mWorkerHandler = new Handler(LauncherModel.getWorkerLooper(), mWorkerCallback);
mUiHandler = new Handler(Looper.getMainLooper(), mUiCallback);
+ sNotificationListenerInstance = this;
}
- public static @Nullable NotificationListener getInstance() {
- return sNotificationListenerInstance;
+ public static @Nullable NotificationListener getInstanceIfConnected() {
+ return sIsConnected ? sNotificationListenerInstance : null;
}
public static void setNotificationsChangedListener(NotificationsChangedListener listener) {
@@ -119,9 +125,8 @@ public class NotificationListener extends NotificationListenerService {
}
sNotificationsChangedListener = listener;
- NotificationListener notificationListener = getInstance();
- if (notificationListener != null) {
- notificationListener.onNotificationFullRefresh();
+ if (sNotificationListenerInstance != null) {
+ sNotificationListenerInstance.onNotificationFullRefresh();
}
}
@@ -132,7 +137,7 @@ public class NotificationListener extends NotificationListenerService {
@Override
public void onListenerConnected() {
super.onListenerConnected();
- sNotificationListenerInstance = this;
+ sIsConnected = true;
onNotificationFullRefresh();
}
@@ -143,7 +148,7 @@ public class NotificationListener extends NotificationListenerService {
@Override
public void onListenerDisconnected() {
super.onListenerDisconnected();
- sNotificationListenerInstance = null;
+ sIsConnected = false;
}
@Override
@@ -164,7 +169,7 @@ public class NotificationListener extends NotificationListenerService {
NotificationPostedMsg(StatusBarNotification sbn) {
packageUserKey = PackageUserKey.fromNotification(sbn);
notificationKey = sbn.getKey();
- shouldBeFilteredOut = shouldBeFilteredOut(sbn.getNotification());
+ shouldBeFilteredOut = shouldBeFilteredOut(sbn);
}
}
@@ -188,14 +193,14 @@ public class NotificationListener extends NotificationListenerService {
* Filter out notifications that don't have an intent
* or are headers for grouped notifications.
*
- * TODO: use the system concept of a badged notification instead
+ * @see #shouldBeFilteredOut(StatusBarNotification)
*/
private List<StatusBarNotification> filterNotifications(
StatusBarNotification[] notifications) {
if (notifications == null) return null;
Set<Integer> removedNotifications = new HashSet<>();
for (int i = 0; i < notifications.length; i++) {
- if (shouldBeFilteredOut(notifications[i].getNotification())) {
+ if (shouldBeFilteredOut(notifications[i])) {
removedNotifications.add(i);
}
}
@@ -209,7 +214,14 @@ public class NotificationListener extends NotificationListenerService {
return filteredNotifications;
}
- private boolean shouldBeFilteredOut(Notification notification) {
+ private boolean shouldBeFilteredOut(StatusBarNotification sbn) {
+ if (Utilities.isAtLeastO()) {
+ getCurrentRanking().getRanking(sbn.getKey(), mTempRanking);
+ if (!mTempRanking.canShowBadge()) {
+ return true;
+ }
+ }
+ Notification notification = sbn.getNotification();
boolean isGroupHeader = (notification.flags & Notification.FLAG_GROUP_SUMMARY) != 0;
return (notification.contentIntent == null || isGroupHeader);
}
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index b3425259b..bb2dac0e1 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -16,38 +16,35 @@
package com.android.launcher3.notification;
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
-import android.widget.LinearLayout;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
-import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.Themes;
/**
- * A {@link LinearLayout} that contains a single notification, e.g. icon + title + text.
+ * A {@link android.widget.FrameLayout} that contains a single notification,
+ * e.g. icon + title + text.
*/
-public class NotificationMainView extends LinearLayout implements SwipeHelper.Callback {
-
- private final ArgbEvaluator mArgbEvaluator = new ArgbEvaluator();
+public class NotificationMainView extends FrameLayout implements SwipeHelper.Callback {
private NotificationInfo mNotificationInfo;
+ private ViewGroup mTextAndBackground;
+ private int mBackgroundColor;
private TextView mTitleView;
private TextView mTextView;
- private IconPalette mIconPalette;
- private ColorDrawable mColorBackground;
public NotificationMainView(Context context) {
this(context, null, 0);
@@ -65,14 +62,15 @@ public class NotificationMainView extends LinearLayout implements SwipeHelper.Ca
protected void onFinishInflate() {
super.onFinishInflate();
- mTitleView = (TextView) findViewById(R.id.title);
- mTextView = (TextView) findViewById(R.id.text);
- }
-
- public void applyColors(IconPalette iconPalette) {
- mColorBackground = new ColorDrawable(iconPalette.backgroundColor);
- setBackground(mColorBackground);
- mIconPalette = iconPalette;
+ mTextAndBackground = (ViewGroup) findViewById(R.id.text_and_background);
+ ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
+ mBackgroundColor = colorBackground.getColor();
+ RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
+ Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
+ colorBackground, null);
+ mTextAndBackground.setBackground(rippleBackground);
+ mTitleView = (TextView) mTextAndBackground.findViewById(R.id.title);
+ mTextView = (TextView) mTextAndBackground.findViewById(R.id.text);
}
public void applyNotificationInfo(NotificationInfo mainNotification, View iconView) {
@@ -84,30 +82,18 @@ public class NotificationMainView extends LinearLayout implements SwipeHelper.Ca
*/
public void applyNotificationInfo(NotificationInfo mainNotification, View iconView,
boolean animate) {
- if (animate) {
- mTitleView.setAlpha(0);
- mTextView.setAlpha(0);
- mColorBackground.setColor(mIconPalette.secondaryColor);
- }
mNotificationInfo = mainNotification;
mTitleView.setText(mNotificationInfo.title);
mTextView.setText(mNotificationInfo.text);
- iconView.setBackground(mNotificationInfo.getIconForBackground(
- getContext(), mIconPalette.backgroundColor));
+ iconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
+ mBackgroundColor));
setOnClickListener(mNotificationInfo);
setTranslationX(0);
// Add a dummy ItemInfo so that logging populates the correct container and item types
// instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively.
setTag(new ItemInfo());
if (animate) {
- AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
- Animator textFade = ObjectAnimator.ofFloat(mTextView, View.ALPHA, 1);
- Animator titleFade = ObjectAnimator.ofFloat(mTitleView, View.ALPHA, 1);
- ValueAnimator colorChange = ObjectAnimator.ofObject(mColorBackground, "color",
- mArgbEvaluator, mIconPalette.secondaryColor, mIconPalette.backgroundColor);
- animation.playTogether(textFade, titleFade, colorChange);
- animation.setDuration(150);
- animation.start();
+ ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start();
}
}
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index d34727c8d..b8d38f587 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -27,14 +27,12 @@ import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
-import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
-import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -60,19 +58,18 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.anim.PropertyResetListener;
import com.android.launcher3.badge.BadgeInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.graphics.TriangleShape;
import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.shortcuts.ShortcutsItemView;
import com.android.launcher3.util.PackageUserKey;
-import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -84,18 +81,17 @@ import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
* A container for shortcuts to deep links within apps.
*/
@TargetApi(Build.VERSION_CODES.N)
-public class PopupContainerWithArrow extends AbstractFloatingView
- implements View.OnLongClickListener, View.OnTouchListener, DragSource,
+public class PopupContainerWithArrow extends AbstractFloatingView implements DragSource,
DragController.DragListener {
- private final Point mIconShift = new Point();
- private final Point mIconLastTouchPos = new Point();
-
protected final Launcher mLauncher;
private final int mStartDragThreshold;
private LauncherAccessibilityDelegate mAccessibilityDelegate;
private final boolean mIsRtl;
+ public ShortcutsItemView mShortcutsItemView;
+ private NotificationItemView mNotificationItemView;
+
protected BubbleTextView mOriginalIcon;
private final Rect mTempRect = new Rect();
private PointF mInterceptTouchDown = new PointF();
@@ -177,6 +173,8 @@ public class PopupContainerWithArrow extends AbstractFloatingView
boolean reverseOrder = mIsAboveIcon;
if (reverseOrder) {
removeAllViews();
+ mNotificationItemView = null;
+ mShortcutsItemView = null;
itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate);
addDummyViews(originalIcon, itemsToPopulate, notificationKeys.length > 1);
@@ -184,32 +182,20 @@ public class PopupContainerWithArrow extends AbstractFloatingView
orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
}
- List<DeepShortcutView> shortcutViews = new ArrayList<>();
- NotificationItemView notificationView = null;
- for (int i = 0; i < getChildCount(); i++) {
- View item = getChildAt(i);
- switch (itemsToPopulate[i]) {
- case SHORTCUT:
- if (reverseOrder) {
- shortcutViews.add(0, (DeepShortcutView) item);
- } else {
- shortcutViews.add((DeepShortcutView) item);
- }
- break;
- case NOTIFICATION:
- notificationView = (NotificationItemView) item;
- IconPalette iconPalette = originalIcon.getIconPalette();
- notificationView.applyColors(iconPalette);
- break;
- }
+ ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
+ List<DeepShortcutView> shortcutViews = mShortcutsItemView == null
+ ? Collections.EMPTY_LIST
+ : mShortcutsItemView.getDeepShortcutViews(reverseOrder);
+ if (mNotificationItemView != null) {
+ BadgeInfo badgeInfo = mLauncher.getPopupDataProvider()
+ .getBadgeInfoForItem(originalItemInfo);
+ updateNotificationHeader(badgeInfo);
}
// Add the arrow.
mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
mArrow.setPivotX(arrowWidth / 2);
mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
- PopupItemView firstItem = getItemViewAt(mIsAboveIcon ? getItemCount() - 1 : 0);
- mArrow.setBackgroundTintList(firstItem.getAttachedArrowColor());
animateOpen();
@@ -220,30 +206,47 @@ public class PopupContainerWithArrow extends AbstractFloatingView
// Load the shortcuts on a background thread and update the container as it animates.
final Looper workerLooper = LauncherModel.getWorkerLooper();
new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
- mLauncher, (ItemInfo) originalIcon.getTag(), new Handler(Looper.getMainLooper()),
- this, shortcutIds, shortcutViews, notificationKeys, notificationView));
+ mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
+ this, shortcutIds, shortcutViews, notificationKeys, mNotificationItemView));
}
private void addDummyViews(BubbleTextView originalIcon,
- PopupPopulator.Item[] itemsToPopulate, boolean secondaryNotificationViewHasIcons) {
+ PopupPopulator.Item[] itemTypesToPopulate, boolean notificationFooterHasIcons) {
final Resources res = getResources();
- final int spacing = res.getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
+ final int spacing = res.getDimensionPixelSize(R.dimen.popup_items_spacing);
final LayoutInflater inflater = mLauncher.getLayoutInflater();
- int numItems = itemsToPopulate.length;
+ int numItems = itemTypesToPopulate.length;
for (int i = 0; i < numItems; i++) {
- final PopupItemView item = (PopupItemView) inflater.inflate(
- itemsToPopulate[i].layoutId, this, false);
- if (itemsToPopulate[i] == PopupPopulator.Item.NOTIFICATION) {
- int secondaryHeight = secondaryNotificationViewHasIcons ?
- res.getDimensionPixelSize(R.dimen.notification_footer_height) :
- res.getDimensionPixelSize(R.dimen.notification_footer_collapsed_height);
- item.findViewById(R.id.footer).getLayoutParams().height = secondaryHeight;
- }
- if (i < numItems - 1) {
- ((LayoutParams) item.getLayoutParams()).bottomMargin = spacing;
+ PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i];
+ final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false);
+
+ if (itemTypeToPopulate == PopupPopulator.Item.NOTIFICATION) {
+ mNotificationItemView = (NotificationItemView) item;
+ int footerHeight = notificationFooterHasIcons ?
+ res.getDimensionPixelSize(R.dimen.notification_footer_height) : 0;
+ item.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
}
+
+ boolean itemIsFollowedByDifferentType = i < numItems - 1
+ && itemTypesToPopulate[i + 1] != itemTypeToPopulate;
+
item.setAccessibilityDelegate(mAccessibilityDelegate);
- addView(item);
+ if (itemTypeToPopulate == PopupPopulator.Item.SHORTCUT) {
+ if (mShortcutsItemView == null) {
+ mShortcutsItemView = (ShortcutsItemView) inflater.inflate(
+ R.layout.shortcuts_item, this, false);
+ addView(mShortcutsItemView);
+ }
+ mShortcutsItemView.addDeepShortcutView((DeepShortcutView) item);
+ if (itemIsFollowedByDifferentType) {
+ ((LayoutParams) mShortcutsItemView.getLayoutParams()).bottomMargin = spacing;
+ }
+ } else {
+ addView(item);
+ if (itemIsFollowedByDifferentType) {
+ ((LayoutParams) item.getLayoutParams()).bottomMargin = spacing;
+ }
+ }
}
// TODO: update this, since not all items are shortcuts
setContentDescription(getContext().getString(R.string.shortcuts_menu_description,
@@ -535,48 +538,26 @@ public class PopupContainerWithArrow extends AbstractFloatingView
return true;
}
- @Override
- public boolean onTouch(View v, MotionEvent ev) {
- // Touched a shortcut, update where it was touched so we can drag from there on long click.
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_MOVE:
- mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
- break;
+ /**
+ * Updates the notification header to reflect the badge info. Since this can be called
+ * for any badge info (not necessarily the one associated with this app), we first
+ * check that the ItemInfo matches the one of this popup.
+ */
+ public void updateNotificationHeader(BadgeInfo badgeInfo, ItemInfo originalItemInfo) {
+ if (originalItemInfo != mOriginalIcon.getTag()) {
+ return;
}
- return false;
+ updateNotificationHeader(badgeInfo);
}
- public boolean onLongClick(View v) {
- // Return early if this is not initiated from a touch or not the correct view
- if (!v.isInTouchMode() || !(v.getParent() instanceof DeepShortcutView)) return false;
- // Return early if global dragging is not enabled
- if (!mLauncher.isDraggingEnabled()) return false;
- // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
- if (mLauncher.getDragController().isDragging()) return false;
-
- // Long clicked on a shortcut.
- mDeferContainerRemoval = true;
- DeepShortcutView sv = (DeepShortcutView) v.getParent();
- sv.setWillDrawIcon(false);
-
- // Move the icon to align with the center-top of the touch point
- mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
- mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
-
- DragView dv = mLauncher.getWorkspace().beginDragShared(
- sv.getBubbleText(), this, sv.getFinalInfo(),
- new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
- dv.animateShift(-mIconShift.x, -mIconShift.y);
-
- // TODO: support dragging from within folder without having to close it
- AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
- return false;
+ private void updateNotificationHeader(BadgeInfo badgeInfo) {
+ if (mNotificationItemView != null && badgeInfo != null) {
+ mNotificationItemView.updateHeader(badgeInfo.getNotificationCount());
+ }
}
public void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) {
- final NotificationItemView notificationView = (NotificationItemView) findViewById(R.id.notification_view);
- if (notificationView == null) {
+ if (mNotificationItemView == null) {
return;
}
ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
@@ -585,12 +566,11 @@ public class PopupContainerWithArrow extends AbstractFloatingView
AnimatorSet removeNotification = LauncherAnimUtils.createAnimatorSet();
final int duration = getResources().getInteger(
R.integer.config_removeNotificationViewDuration);
- final int spacing = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
- removeNotification.play(animateTranslationYBy(notificationView.getHeight() + spacing,
- duration));
- Animator reduceHeight = notificationView.createRemovalAnimation(duration);
+ final int spacing = getResources().getDimensionPixelSize(R.dimen.popup_items_spacing);
+ removeNotification.play(reduceNotificationViewHeight(
+ mNotificationItemView.getHeight() + spacing, duration, mNotificationItemView));
final View removeMarginView = mIsAboveIcon ? getItemViewAt(getItemCount() - 2)
- : notificationView;
+ : mNotificationItemView;
if (removeMarginView != null) {
ValueAnimator removeMargin = ValueAnimator.ofFloat(1, 0).setDuration(duration);
removeMargin.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@@ -602,19 +582,16 @@ public class PopupContainerWithArrow extends AbstractFloatingView
});
removeNotification.play(removeMargin);
}
- removeNotification.play(reduceHeight);
- Animator fade = ObjectAnimator.ofFloat(notificationView, ALPHA, 0)
+ Animator fade = ObjectAnimator.ofFloat(mNotificationItemView, ALPHA, 0)
.setDuration(duration);
fade.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- removeView(notificationView);
+ removeView(mNotificationItemView);
if (getItemCount() == 0) {
close(false);
return;
}
- View firstItem = getItemViewAt(mIsAboveIcon ? getItemCount() - 1 : 0);
- mArrow.setBackgroundTintList(firstItem.getBackgroundTintList());
}
});
removeNotification.play(fade);
@@ -628,7 +605,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView
removeNotification.start();
return;
}
- notificationView.trimNotifications(badgeInfo.getNotificationKeys());
+ mNotificationItemView.trimNotifications(badgeInfo.getNotificationKeys());
}
private ObjectAnimator createArrowScaleAnim(float scale) {
@@ -636,16 +613,42 @@ public class PopupContainerWithArrow extends AbstractFloatingView
mArrow, new PropertyListBuilder().scale(scale).build());
}
/**
- * Animates the translationY of this container if it is open above the icon.
- * If it is below the icon, the container already shifts up when the height
- * of a child (e.g. NotificationView) changes, so the translation isn't necessary.
+ * Animates the height of the notification item and the translationY of other items accordingly.
*/
- public @Nullable Animator animateTranslationYBy(int translationY, int duration) {
+ public Animator reduceNotificationViewHeight(int heightToRemove, int duration,
+ NotificationItemView notificationItem) {
+ final int translateYBy = mIsAboveIcon ? heightToRemove : -heightToRemove;
+ AnimatorSet animatorSet = LauncherAnimUtils.createAnimatorSet();
+ animatorSet.play(notificationItem.animateHeightRemoval(heightToRemove));
+ PropertyResetListener<View, Float> resetTranslationYListener
+ = new PropertyResetListener<>(TRANSLATION_Y, 0f);
+ for (int i = 0; i < getItemCount(); i++) {
+ final PopupItemView itemView = getItemViewAt(i);
+ if (!mIsAboveIcon && itemView == notificationItem) {
+ // The notification view is already in the right place when container is below icon.
+ continue;
+ }
+ ValueAnimator translateItem = ObjectAnimator.ofFloat(itemView, TRANSLATION_Y,
+ itemView.getTranslationY() + translateYBy).setDuration(duration);
+ translateItem.addListener(resetTranslationYListener);
+ animatorSet.play(translateItem);
+ }
if (mIsAboveIcon) {
- return ObjectAnimator.ofFloat(this, TRANSLATION_Y, getTranslationY() + translationY)
- .setDuration(duration);
+ // All the items, including the notification item, translated down, but the
+ // container itself did not. This means the items would jump back to their
+ // original translation unless we update the container's translationY here.
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setTranslationY(getTranslationY() + translateYBy);
+ }
+ });
}
- return null;
+ return animatorSet;
+ }
+
+ public Animator reduceNotificationViewHeight(int heightToRemove, int duration) {
+ return reduceNotificationViewHeight(heightToRemove, duration, mNotificationItemView);
}
@Override
@@ -677,6 +680,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
// Either the original icon or one of the shortcuts was dragged.
// Hide the container, but don't remove it yet because that interferes with touch events.
+ mDeferContainerRemoval = true;
animateClose();
}
@@ -698,7 +702,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView
@Override
public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
target.itemType = ItemType.DEEPSHORTCUT;
- target.rank = info.rank;
targetParent.containerType = ContainerType.DEEPSHORTCUTS;
}
@@ -740,38 +743,17 @@ public class PopupContainerWithArrow extends AbstractFloatingView
for (int i = firstOpenItemIndex; i < firstOpenItemIndex + numOpenShortcuts; i++) {
final PopupItemView view = getItemViewAt(i);
Animator anim;
- if (view.willDrawIcon()) {
- anim = view.createCloseAnimation(mIsAboveIcon, mIsLeftAligned, duration);
- int animationIndex = mIsAboveIcon ? i - firstOpenItemIndex
- : numOpenShortcuts - i - 1;
- anim.setStartDelay(stagger * animationIndex);
-
- Animator fadeAnim = ObjectAnimator.ofFloat(view, View.ALPHA, 0);
- // Don't start fading until the arrow is gone.
- fadeAnim.setStartDelay(stagger * animationIndex + arrowScaleDuration);
- fadeAnim.setDuration(duration - arrowScaleDuration);
- fadeAnim.setInterpolator(fadeInterpolator);
- shortcutAnims.play(fadeAnim);
- } else {
- // The view is being dragged. Animate it such that it collapses with the drag view
- anim = view.collapseToIcon();
- anim.setDuration(DragView.VIEW_ZOOM_DURATION);
-
- // Scale and translate the view to follow the drag view.
- Point iconCenter = view.getIconCenter();
- view.setPivotX(iconCenter.x);
- view.setPivotY(iconCenter.y);
-
- float scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / view.getHeight();
- Animator anim2 = LauncherAnimUtils.ofPropertyValuesHolder(view,
- new PropertyListBuilder()
- .scale(scale)
- .translationX(mIconShift.x)
- .translationY(mIconShift.y)
- .build())
- .setDuration(DragView.VIEW_ZOOM_DURATION);
- shortcutAnims.play(anim2);
- }
+ anim = view.createCloseAnimation(mIsAboveIcon, mIsLeftAligned, duration);
+ int animationIndex = mIsAboveIcon ? i - firstOpenItemIndex
+ : numOpenShortcuts - i - 1;
+ anim.setStartDelay(stagger * animationIndex);
+
+ Animator fadeAnim = ObjectAnimator.ofFloat(view, View.ALPHA, 0);
+ // Don't start fading until the arrow is gone.
+ fadeAnim.setStartDelay(stagger * animationIndex + arrowScaleDuration);
+ fadeAnim.setDuration(duration - arrowScaleDuration);
+ fadeAnim.setInterpolator(fadeInterpolator);
+ shortcutAnims.play(fadeAnim);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index e314b646b..ee2930f19 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -172,7 +172,7 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan
private boolean updateBadgeIcon(BadgeInfo badgeInfo) {
boolean hadNotificationToShow = badgeInfo.hasNotificationToShow();
NotificationInfo notificationInfo = null;
- NotificationListener notificationListener = NotificationListener.getInstance();
+ NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
if (notificationListener != null && badgeInfo.getNotificationKeys().size() == 1) {
StatusBarNotification[] activeNotifications = notificationListener
.getActiveNotifications(new String[] {badgeInfo.getNotificationKeys().get(0)});
@@ -222,13 +222,13 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan
/** This makes a potentially expensive binder call and should be run on a background thread. */
public List<StatusBarNotification> getStatusBarNotificationsForKeys(String[] notificationKeys) {
- NotificationListener notificationListener = NotificationListener.getInstance();
+ NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
return notificationListener == null ? Collections.EMPTY_LIST
: notificationListener.getNotificationsForKeys(notificationKeys);
}
public void cancelNotification(String notificationKey) {
- NotificationListener notificationListener = NotificationListener.getInstance();
+ NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
if (notificationListener == null) {
return;
}
diff --git a/src/com/android/launcher3/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java
index 6af6e7d2c..eeece881b 100644
--- a/src/com/android/launcher3/popup/PopupItemView.java
+++ b/src/com/android/launcher3/popup/PopupItemView.java
@@ -20,9 +20,15 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
-import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
+import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
@@ -30,8 +36,7 @@ import android.widget.FrameLayout;
import com.android.launcher3.LogAccelerateInterpolator;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.util.PillRevealOutlineProvider;
-import com.android.launcher3.util.PillWidthRevealOutlineProvider;
+import com.android.launcher3.anim.PillRevealOutlineProvider;
/**
* An abstract {@link FrameLayout} that supports animating an item's content
@@ -42,11 +47,14 @@ public abstract class PopupItemView extends FrameLayout
protected static final Point sTempPoint = new Point();
- private final Rect mPillRect;
+ protected final Rect mPillRect;
private float mOpenAnimationProgress;
protected View mIconView;
+ private final Paint mBackgroundClipPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
+ Paint.FILTER_BITMAP_FLAG);
+
public PopupItemView(Context context) {
this(context, null, 0);
}
@@ -73,12 +81,31 @@ public abstract class PopupItemView extends FrameLayout
mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
- protected ColorStateList getAttachedArrowColor() {
- return getBackgroundTintList();
+ protected void initializeBackgroundClipping(boolean force) {
+ if (force || mBackgroundClipPaint.getShader() == null) {
+ mBackgroundClipPaint.setXfermode(null);
+ mBackgroundClipPaint.setShader(null);
+ Bitmap backgroundBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
+ Bitmap.Config.ALPHA_8);
+ Canvas canvas = new Canvas();
+ canvas.setBitmap(backgroundBitmap);
+ canvas.drawRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(),
+ getBackgroundRadius(), getBackgroundRadius(), mBackgroundClipPaint);
+ Shader backgroundClipShader = new BitmapShader(backgroundBitmap,
+ Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+ mBackgroundClipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
+ mBackgroundClipPaint.setShader(backgroundClipShader);
+ }
}
- public boolean willDrawIcon() {
- return true;
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ initializeBackgroundClipping(false /* force */);
+ int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null,
+ Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+ super.dispatchDraw(canvas);
+ canvas.drawPaint(mBackgroundClipPaint);
+ canvas.restoreToCount(saveCount);
}
/**
@@ -126,17 +153,6 @@ public abstract class PopupItemView extends FrameLayout
}
/**
- * Creates an animator which clips the container to form a circle around the icon.
- */
- public Animator collapseToIcon() {
- int halfHeight = getMeasuredHeight() / 2;
- int iconCenterX = getIconCenter().x;
- return new PillWidthRevealOutlineProvider(mPillRect,
- iconCenterX - halfHeight, iconCenterX + halfHeight)
- .createRevealAnimator(this, true);
- }
-
- /**
* Returns the position of the center of the icon relative to the container.
*/
public Point getIconCenter() {
@@ -147,6 +163,10 @@ public abstract class PopupItemView extends FrameLayout
return sTempPoint;
}
+ protected float getBackgroundRadius() {
+ return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
+ }
+
/**
* Extension of {@link PillRevealOutlineProvider} which scales the icon based on the height.
*/
@@ -161,10 +181,9 @@ public abstract class PopupItemView extends FrameLayout
private final boolean mPivotLeft;
private final float mTranslateX;
- public ZoomRevealOutlineProvider(int x, int y, Rect pillRect,
- View translateView, View zoomView, boolean isContainerAboveIcon, boolean pivotLeft) {
- super(x, y, pillRect, zoomView.getResources().getDimensionPixelSize(
- R.dimen.bg_pill_radius));
+ public ZoomRevealOutlineProvider(int x, int y, Rect pillRect, PopupItemView translateView,
+ View zoomView, boolean isContainerAboveIcon, boolean pivotLeft) {
+ super(x, y, pillRect, translateView.getBackgroundRadius());
mTranslateView = translateView;
mZoomView = zoomView;
mFullHeight = pillRect.height();
@@ -179,8 +198,10 @@ public abstract class PopupItemView extends FrameLayout
public void setProgress(float progress) {
super.setProgress(progress);
- mZoomView.setScaleX(progress);
- mZoomView.setScaleY(progress);
+ if (mZoomView != null) {
+ mZoomView.setScaleX(progress);
+ mZoomView.setScaleY(progress);
+ }
float height = mOutline.height();
mTranslateView.setTranslationY(mTranslateYMultiplier * (mFullHeight - height));
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index d2814ee7b..39c2db2d5 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -196,7 +196,8 @@ public class PopupPopulator {
@Override
public void run() {
- mShortcutChild.applyShortcutInfo(mShortcutChildInfo, mDetail, mContainer);
+ mShortcutChild.applyShortcutInfo(mShortcutChildInfo, mDetail,
+ mContainer.mShortcutsItemView);
}
}
diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java
index b0482f8b2..3e4cd0192 100644
--- a/src/com/android/launcher3/provider/ImportDataTask.java
+++ b/src/com/android/launcher3/provider/ImportDataTask.java
@@ -37,6 +37,7 @@ import com.android.launcher3.DefaultLayoutParser;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
@@ -46,7 +47,6 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.GridSizeMigrationTask;
import com.android.launcher3.util.LongArrayMap;
@@ -112,7 +112,7 @@ public class ImportDataTask {
screenOps.add(ContentProviderOperation.newInsert(
LauncherSettings.WorkspaceScreens.CONTENT_URI).withValues(v).build());
}
- mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY, screenOps);
+ mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, screenOps);
importWorkspaceItems(allScreens.get(0), screenIdMap);
GridSizeMigrationTask.markForMigration(mContext, mMaxGridSizeX, mMaxGridSizeY, mHotseatSize);
@@ -289,7 +289,7 @@ public class ImportDataTask {
}
if (insertOperations.size() >= BATCH_INSERT_SIZE) {
- mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY,
+ mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY,
insertOperations);
insertOperations.clear();
}
@@ -300,7 +300,7 @@ public class ImportDataTask {
throw new Exception("Insufficient data");
}
if (!insertOperations.isEmpty()) {
- mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY,
+ mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY,
insertOperations);
insertOperations.clear();
}
@@ -319,7 +319,7 @@ public class ImportDataTask {
mHotseatSize = (int) hotseatItems.keyAt(hotseatItems.size() - 1) + 1;
if (!insertOperations.isEmpty()) {
- mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY,
+ mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY,
insertOperations);
}
}
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index 38a3e1f58..4dc3c1c0d 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -40,6 +40,7 @@ import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
/**
* A frame layout which contains a QSB. This internally uses fragment to bind the view, which
@@ -89,7 +90,11 @@ public class QsbContainerView extends FrameLayout {
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mWrapper = new FrameLayout(getActivity());
- mWrapper.addView(createQsb(mWrapper));
+
+ // Only add the view when enabled
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ mWrapper.addView(createQsb(mWrapper));
+ }
return mWrapper;
}
@@ -197,6 +202,11 @@ public class QsbContainerView extends FrameLayout {
}
private void rebindFragment() {
+ // Exit if the embedded qsb is disabled
+ if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ return;
+ }
+
if (mWrapper != null && getActivity() != null) {
mWrapper.removeAllViews();
mWrapper.addView(createQsb(mWrapper));
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
index 941391362..df7f6954d 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -65,7 +65,8 @@ public class DeepShortcutManager {
}
public static boolean supportsShortcuts(ItemInfo info) {
- return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+ return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+ && !info.isDisabled();
}
public boolean wasLastCallSuccess() {
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 2f07c9ac9..47a023e25 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -17,26 +17,30 @@
package com.android.launcher3.shortcuts;
import android.content.Context;
+import android.graphics.Point;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
+import android.widget.FrameLayout;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.popup.PopupItemView;
+import com.android.launcher3.Utilities;
/**
* A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
* This lets us animate the DeepShortcutView (icon and text) separately from the background.
*/
-public class DeepShortcutView extends PopupItemView {
+public class DeepShortcutView extends FrameLayout {
+
+ private static final Point sTempPoint = new Point();
private final Rect mPillRect;
private DeepShortcutTextView mBubbleText;
+ private View mIconView;
private ShortcutInfo mInfo;
private ShortcutInfoCompat mDetail;
@@ -59,6 +63,7 @@ public class DeepShortcutView extends PopupItemView {
protected void onFinishInflate() {
super.onFinishInflate();
mBubbleText = (DeepShortcutTextView) findViewById(R.id.deep_shortcut);
+ mIconView = findViewById(R.id.icon);
}
public DeepShortcutTextView getBubbleText() {
@@ -73,6 +78,17 @@ public class DeepShortcutView extends PopupItemView {
return mIconView.getVisibility() == View.VISIBLE;
}
+ /**
+ * Returns the position of the center of the icon relative to the container.
+ */
+ public Point getIconCenter() {
+ sTempPoint.y = sTempPoint.x = getMeasuredHeight() / 2;
+ if (Utilities.isRtl(getResources())) {
+ sTempPoint.x = getMeasuredWidth() - sTempPoint.x;
+ }
+ return sTempPoint;
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -81,7 +97,7 @@ public class DeepShortcutView extends PopupItemView {
/** package private **/
public void applyShortcutInfo(ShortcutInfo info, ShortcutInfoCompat detail,
- PopupContainerWithArrow container) {
+ ShortcutsItemView container) {
mInfo = info;
mDetail = detail;
mBubbleText.applyFromShortcutInfo(info);
diff --git a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
new file mode 100644
index 000000000..349c4c946
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
@@ -0,0 +1,180 @@
+/*
+ * 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.shortcuts;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.popup.PopupItemView;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A {@link PopupItemView} that contains all of the {@link DeepShortcutView}s for an app.
+ */
+public class ShortcutsItemView extends PopupItemView implements View.OnLongClickListener,
+ View.OnTouchListener, LogContainerProvider {
+
+ private Launcher mLauncher;
+ private LinearLayout mDeepShortcutsLayout;
+ private final Point mIconShift = new Point();
+ private final Point mIconLastTouchPos = new Point();
+
+ public ShortcutsItemView(Context context) {
+ this(context, null, 0);
+ }
+
+ public ShortcutsItemView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ShortcutsItemView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mLauncher = Launcher.getLauncher(context);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mDeepShortcutsLayout = (LinearLayout) findViewById(R.id.deep_shortcuts);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ // Touched a shortcut, update where it was touched so we can drag from there on long click.
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ // Return early if this is not initiated from a touch or not the correct view
+ if (!v.isInTouchMode() || !(v.getParent() instanceof DeepShortcutView)) return false;
+ // Return early if global dragging is not enabled
+ if (!mLauncher.isDraggingEnabled()) return false;
+ // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
+ if (mLauncher.getDragController().isDragging()) return false;
+
+ // Long clicked on a shortcut.
+ DeepShortcutView sv = (DeepShortcutView) v.getParent();
+ sv.setWillDrawIcon(false);
+
+ // Move the icon to align with the center-top of the touch point
+ mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
+ mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
+
+ DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getBubbleText(),
+ (PopupContainerWithArrow) getParent(), sv.getFinalInfo(),
+ new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
+ dv.animateShift(-mIconShift.x, -mIconShift.y);
+
+ // TODO: support dragging from within folder without having to close it
+ AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
+ return false;
+ }
+
+ public void addDeepShortcutView(DeepShortcutView deepShortcutView) {
+ if (getNumDeepShortcuts() > 0) {
+ getDeepShortcutAt(getNumDeepShortcuts() - 1).findViewById(R.id.divider)
+ .setVisibility(VISIBLE);
+ }
+ mDeepShortcutsLayout.addView(deepShortcutView);
+ }
+
+ private DeepShortcutView getDeepShortcutAt(int index) {
+ return (DeepShortcutView) mDeepShortcutsLayout.getChildAt(index);
+ }
+
+ private int getNumDeepShortcuts() {
+ return mDeepShortcutsLayout.getChildCount();
+ }
+
+ public List<DeepShortcutView> getDeepShortcutViews(boolean reverseOrder) {
+ int numDeepShortcuts = getNumDeepShortcuts();
+ List<DeepShortcutView> deepShortcutViews = new ArrayList<>(numDeepShortcuts);
+ for (int i = 0; i < numDeepShortcuts; i++) {
+ DeepShortcutView deepShortcut = getDeepShortcutAt(i);
+ if (reverseOrder) {
+ deepShortcutViews.add(0, deepShortcut);
+ } else {
+ deepShortcutViews.add(deepShortcut);
+ }
+ }
+ return deepShortcutViews;
+ }
+
+ @Override
+ public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
+ AnimatorSet openAnimation = LauncherAnimUtils.createAnimatorSet();
+ openAnimation.play(super.createOpenAnimation(isContainerAboveIcon, pivotLeft));
+ for (int i = 0; i < getNumDeepShortcuts(); i++) {
+ View deepShortcutIcon = getDeepShortcutAt(i).getIconView();
+ deepShortcutIcon.setScaleX(0);
+ deepShortcutIcon.setScaleY(0);
+ openAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder(
+ deepShortcutIcon, new PropertyListBuilder().scale(1).build()));
+ }
+ return openAnimation;
+ }
+
+ @Override
+ public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
+ long duration) {
+ AnimatorSet closeAnimation = LauncherAnimUtils.createAnimatorSet();
+ closeAnimation.play(super.createCloseAnimation(isContainerAboveIcon, pivotLeft, duration));
+ for (int i = 0; i < getNumDeepShortcuts(); i++) {
+ View deepShortcutIcon = getDeepShortcutAt(i).getIconView();
+ deepShortcutIcon.setScaleX(1);
+ deepShortcutIcon.setScaleY(1);
+ closeAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder(
+ deepShortcutIcon, new PropertyListBuilder().scale(0).build()));
+ }
+ return closeAnimation;
+ }
+
+ @Override
+ public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+ LauncherLogProto.Target targetParent) {
+ target.itemType = LauncherLogProto.ItemType.DEEPSHORTCUT;
+ target.rank = info.rank;
+ targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
+ }
+}
diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java
index 6797c7ba3..aedca8db4 100644
--- a/src/com/android/launcher3/testing/LauncherExtension.java
+++ b/src/com/android/launcher3/testing/LauncherExtension.java
@@ -180,9 +180,6 @@ public class LauncherExtension extends Launcher {
}
@Override
- public UserEventDispatcher getUserEventDispatcher() { return null; }
-
- @Override
public View getQsbBar() {
return null;
}
diff --git a/src/com/android/launcher3/util/LooperExecuter.java b/src/com/android/launcher3/util/LooperExecutor.java
index 4db999bce..5b7c20bbf 100644
--- a/src/com/android/launcher3/util/LooperExecuter.java
+++ b/src/com/android/launcher3/util/LooperExecutor.java
@@ -25,11 +25,11 @@ import java.util.concurrent.TimeUnit;
/**
* Extension of {@link AbstractExecutorService} which executed on a provided looper.
*/
-public class LooperExecuter extends AbstractExecutorService {
+public class LooperExecutor extends AbstractExecutorService {
private final Handler mHandler;
- public LooperExecuter(Looper looper) {
+ public LooperExecutor(Looper looper) {
mHandler = new Handler(looper);
}
diff --git a/src/com/android/launcher3/util/LooperIdleLock.java b/src/com/android/launcher3/util/LooperIdleLock.java
new file mode 100644
index 000000000..35cac14e3
--- /dev/null
+++ b/src/com/android/launcher3/util/LooperIdleLock.java
@@ -0,0 +1,71 @@
+/*
+ * 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.util;
+
+import android.os.Looper;
+import android.os.MessageQueue;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Utility class to block execution until the UI looper is idle.
+ */
+public class LooperIdleLock implements MessageQueue.IdleHandler, Runnable {
+
+ private final Object mLock;
+
+ private boolean mIsLocked;
+
+ public LooperIdleLock(Object lock, Looper looper) {
+ mLock = lock;
+ mIsLocked = true;
+ if (Utilities.ATLEAST_MARSHMALLOW) {
+ looper.getQueue().addIdleHandler(this);
+ } else {
+ // Looper.myQueue() only gives the current queue. Move the execution to the UI thread
+ // so that the IdleHandler is attached to the correct message queue.
+ new LooperExecutor(looper).execute(this);
+ }
+ }
+
+ @Override
+ public void run() {
+ Looper.myQueue().addIdleHandler(this);
+ }
+
+ @Override
+ public boolean queueIdle() {
+ synchronized (mLock) {
+ mIsLocked = false;
+ mLock.notify();
+ }
+ return false;
+ }
+
+ public boolean awaitLocked(long ms) {
+ if (mIsLocked) {
+ try {
+ // Just in case mFlushingWorkerThread changes but we aren't woken up,
+ // wait no longer than 1sec at a time
+ mLock.wait(ms);
+ } catch (InterruptedException ex) {
+ // Ignore
+ }
+ }
+ return mIsLocked;
+ }
+}
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index 577d19f8c..85a000cd8 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -21,6 +21,7 @@ import android.content.SharedPreferences;
import android.content.pm.LauncherActivityInfo;
import android.os.Process;
import android.os.UserHandle;
+import android.support.v4.os.BuildCompat;
import com.android.launcher3.AppInfo;
import com.android.launcher3.FolderInfo;
@@ -31,6 +32,7 @@ import com.android.launcher3.LauncherFiles;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
+import com.android.launcher3.SessionCommitReceiver;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
@@ -68,12 +70,15 @@ public class ManagedProfileHeuristic {
private final LauncherModel mModel;
private final UserHandle mUser;
private final IconCache mIconCache;
+ private final boolean mAddIconsToHomescreen;
private ManagedProfileHeuristic(Context context, UserHandle user) {
mContext = context;
mUser = user;
mModel = LauncherAppState.getInstance(context).getModel();
mIconCache = LauncherAppState.getInstance(context).getIconCache();
+ mAddIconsToHomescreen =
+ !BuildCompat.isAtLeastO() || SessionCommitReceiver.isEnabled(context);
}
public void processPackageRemoved(String[] packages) {
@@ -127,7 +132,7 @@ public class ManagedProfileHeuristic {
// Do not add shortcuts on the homescreen for the first time. This prevents the launcher
// getting filled with the managed user apps, when it start with a fresh DB (or after
// a very long time).
- if (userAppsExisted && !homescreenApps.isEmpty()) {
+ if (userAppsExisted && !homescreenApps.isEmpty() && mAddIconsToHomescreen) {
mModel.addAndBindAddedWorkspaceItems(new ArrayList<ItemInfo>(homescreenApps));
}
}
@@ -147,6 +152,13 @@ public class ManagedProfileHeuristic {
}
// Try to get a work folder.
String folderIdKey = USER_FOLDER_ID_PREFIX + mUserManager.getSerialNumberForUser(user);
+ if (!mAddIconsToHomescreen) {
+ if (!mPrefs.contains(folderIdKey)) {
+ // Just mark the folder id preference to avoid new folder creation later.
+ mPrefs.edit().putLong(folderIdKey, -1).apply();
+ }
+ return;
+ }
if (mPrefs.contains(folderIdKey)) {
long folderId = mPrefs.getLong(folderIdKey, 0);
final FolderInfo workFolder = mModel.findFolderById(folderId);
@@ -163,6 +175,7 @@ public class ManagedProfileHeuristic {
@Override
public void run() {
+ workFolder.prepareAutoUpdate();
for (ShortcutInfo info : workFolderApps) {
workFolder.add(info, false);
}
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index e89fc0ca0..7629f7822 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -40,63 +40,55 @@ import java.util.List;
*/
public class PackageManagerHelper {
- private static final int FLAG_SUSPENDED = 1<<30;
-
private final Context mContext;
private final PackageManager mPm;
+ private final LauncherAppsCompat mLauncherApps;
public PackageManagerHelper(Context context) {
mContext = context;
mPm = context.getPackageManager();
+ mLauncherApps = LauncherAppsCompat.getInstance(context);
}
/**
* Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't
* guarantee that the app is on SD card.
*/
- public boolean isAppOnSdcard(String packageName) {
- return isAppEnabled(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
- }
-
- public boolean isAppEnabled(String packageName) {
- return isAppEnabled(packageName, 0);
- }
-
- public boolean isAppEnabled(String packageName, int flags) {
- try {
- ApplicationInfo info = mPm.getApplicationInfo(packageName, flags);
- return info != null && info.enabled;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
+ public boolean isAppOnSdcard(String packageName, UserHandle user) {
+ ApplicationInfo info = mLauncherApps.getApplicationInfo(
+ packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, user);
+ return info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
}
- public boolean isAppSuspended(String packageName) {
- try {
- ApplicationInfo info = mPm.getApplicationInfo(packageName, 0);
- return info != null && isAppSuspended(info);
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
+ /**
+ * Returns whether the target app is suspended for a given user as per
+ * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
+ */
+ public boolean isAppSuspended(String packageName, UserHandle user) {
+ ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, 0, user);
+ return info != null && isAppSuspended(info);
}
public boolean isSafeMode() {
- return mPm.isSafeMode();
+ return mContext.getPackageManager().isSafeMode();
}
public Intent getAppLaunchIntent(String pkg, UserHandle user) {
- List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(mContext)
- .getActivityList(pkg, user);
+ List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(pkg, user);
return activities.isEmpty() ? null :
AppInfo.makeLaunchIntent(mContext, activities.get(0), user);
}
+ /**
+ * Returns whether an application is suspended as per
+ * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
+ */
public static boolean isAppSuspended(ApplicationInfo info) {
// The value of FLAG_SUSPENDED was reused by a hidden constant
// ApplicationInfo.FLAG_PRIVILEGED prior to N, so only check for suspended flag on N
// or later.
if (Utilities.ATLEAST_NOUGAT) {
- return (info.flags & FLAG_SUSPENDED) != 0;
+ return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
} else {
return false;
}
diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java
index 89353e110..7ab0d3103 100644
--- a/src/com/android/launcher3/util/Preconditions.java
+++ b/src/com/android/launcher3/util/Preconditions.java
@@ -19,7 +19,7 @@ package com.android.launcher3.util;
import android.os.Looper;
import com.android.launcher3.LauncherModel;
-import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.config.FeatureFlags;
/**
* A set of utility methods for thread verification.
@@ -27,25 +27,25 @@ import com.android.launcher3.config.ProviderConfig;
public class Preconditions {
public static void assertNotNull(Object o) {
- if (ProviderConfig.IS_DOGFOOD_BUILD && o == null) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD && o == null) {
throw new IllegalStateException();
}
}
public static void assertWorkerThread() {
- if (ProviderConfig.IS_DOGFOOD_BUILD && !isSameLooper(LauncherModel.getWorkerLooper())) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(LauncherModel.getWorkerLooper())) {
throw new IllegalStateException();
}
}
public static void assertUIThread() {
- if (ProviderConfig.IS_DOGFOOD_BUILD && !isSameLooper(Looper.getMainLooper())) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(Looper.getMainLooper())) {
throw new IllegalStateException();
}
}
public static void assertNonUiThread() {
- if (ProviderConfig.IS_DOGFOOD_BUILD && isSameLooper(Looper.getMainLooper())) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD && isSameLooper(Looper.getMainLooper())) {
throw new IllegalStateException();
}
}
diff --git a/src/com/android/launcher3/util/SQLiteCacheHelper.java b/src/com/android/launcher3/util/SQLiteCacheHelper.java
index 1ff6293a0..5344416ea 100644
--- a/src/com/android/launcher3/util/SQLiteCacheHelper.java
+++ b/src/com/android/launcher3/util/SQLiteCacheHelper.java
@@ -10,7 +10,7 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.config.FeatureFlags;
/**
* An extension of {@link SQLiteOpenHelper} with utility methods for a single table cache DB.
@@ -19,7 +19,7 @@ import com.android.launcher3.config.ProviderConfig;
public abstract class SQLiteCacheHelper {
private static final String TAG = "SQLiteCacheHelper";
- private static final boolean NO_ICON_CACHE = ProviderConfig.IS_DOGFOOD_BUILD &&
+ private static final boolean NO_ICON_CACHE = FeatureFlags.IS_DOGFOOD_BUILD &&
Utilities.isPropertyEnabled(LogConfig.MEMORY_ONLY_ICON_CACHE);
private final String mTableName;
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index acd589e20..d86333998 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -18,6 +18,8 @@ package com.android.launcher3.util;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
import android.view.ContextThemeWrapper;
/**
@@ -49,4 +51,21 @@ public class Themes {
ta.recycle();
return (int) (255 * alpha + 0.5f);
}
+
+ /**
+ * Scales a color matrix such that, when applied to color R G B A, it produces R' G' B' A' where
+ * R' = r * R
+ * G' = g * G
+ * B' = b * B
+ * A' = a * A
+ *
+ * The matrix will, for instance, turn white into r g b a, and black will remain black.
+ *
+ * @param color The color r g b a
+ * @param target The ColorMatrix to scale
+ */
+ public static void setColorScaleOnMatrix(int color, ColorMatrix target) {
+ target.setScale(Color.red(color) / 255f, Color.green(color) / 255f,
+ Color.blue(color) / 255f, Color.alpha(color) / 255f);
+ }
}
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
index 9bd288244..4cb6ca831 100644
--- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -16,12 +16,10 @@
package com.android.launcher3.util;
-import android.util.Log;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewTreeObserver.OnDrawListener;
-import com.android.launcher3.DeferredHandler;
import com.android.launcher3.Launcher;
import java.util.ArrayList;
@@ -34,7 +32,7 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable,
OnAttachStateChangeListener {
private final ArrayList<Runnable> mTasks = new ArrayList<>();
- private final DeferredHandler mHandler;
+ private final Executor mExecutor;
private Launcher mLauncher;
private View mAttachedView;
@@ -43,8 +41,8 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable,
private boolean mLoadAnimationCompleted;
private boolean mFirstDrawCompleted;
- public ViewOnDrawExecutor(DeferredHandler handler) {
- mHandler = handler;
+ public ViewOnDrawExecutor(Executor executor) {
+ mExecutor = executor;
}
public void attachTo(Launcher launcher) {
@@ -92,7 +90,7 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable,
// Post the pending tasks after both onDraw and onLoadAnimationCompleted have been called.
if (mLoadAnimationCompleted && mFirstDrawCompleted && !mCompleted) {
for (final Runnable r : mTasks) {
- mHandler.post(r);
+ mExecutor.execute(r);
}
markCompleted();
}