diff options
Diffstat (limited to 'src')
157 files changed, 6875 insertions, 4225 deletions
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index 5b42cad96..8ac857082 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -18,10 +18,16 @@ package com.android.launcher3; import android.content.ComponentName; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; +import android.os.Process; import android.os.UserHandle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.ItemInfoMatcher; @@ -34,18 +40,18 @@ import java.util.List; * Stores the list of all applications for the all apps view. */ public class AllAppsList { + private static final String TAG = "AllAppsList"; + public static final int DEFAULT_APPLICATIONS_NUMBER = 42; /** The list off all apps. */ - public ArrayList<AppInfo> data = - new ArrayList<AppInfo>(DEFAULT_APPLICATIONS_NUMBER); + public final ArrayList<AppInfo> data = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER); /** The list of apps that have been added since the last notify() call. */ - public ArrayList<AppInfo> added = - new ArrayList<AppInfo>(DEFAULT_APPLICATIONS_NUMBER); + public ArrayList<AppInfo> added = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER); /** The list of apps that have been removed since the last notify() call. */ - public ArrayList<AppInfo> removed = new ArrayList<AppInfo>(); + public ArrayList<AppInfo> removed = new ArrayList<>(); /** The list of apps that have been modified since the last notify() call. */ - public ArrayList<AppInfo> modified = new ArrayList<AppInfo>(); + public ArrayList<AppInfo> modified = new ArrayList<>(); private IconCache mIconCache; @@ -69,7 +75,7 @@ public class AllAppsList { if (!mAppFilter.shouldShowApp(info.componentName)) { return; } - if (findActivity(data, info.componentName, info.user)) { + if (findAppInfo(info.componentName, info.user) != null) { return; } mIconCache.getTitleAndIcon(info, activityInfo, true /* useLowResIcon */); @@ -78,6 +84,25 @@ public class AllAppsList { added.add(info); } + public void addPromiseApp(Context context, + PackageInstallerCompat.PackageInstallInfo installInfo) { + ApplicationInfo applicationInfo = LauncherAppsCompat.getInstance(context) + .getApplicationInfo(installInfo.packageName, 0, Process.myUserHandle()); + // only if not yet installed + if (applicationInfo == null) { + PromiseAppInfo info = new PromiseAppInfo(installInfo); + mIconCache.getTitleAndIcon(info, info.usingLowResIcon); + data.add(info); + added.add(info); + } + } + + public void removePromiseApp(AppInfo appInfo) { + // the <em>removed</em> list is handled by the caller + // so not adding it here + data.remove(appInfo); + } + public void clear() { data.clear(); // TODO: do we clear these too? @@ -160,6 +185,7 @@ public class AllAppsList { if (user.equals(applicationInfo.user) && packageName.equals(applicationInfo.componentName.getPackageName())) { if (!findActivity(matches, applicationInfo.componentName)) { + Log.w(TAG, "Shortcut will be removed due to app component name change."); removed.add(applicationInfo); data.remove(i); } @@ -169,9 +195,7 @@ public class AllAppsList { // Find enabled activities and add them to the adapter // Also updates existing activities with new labels/icons for (final LauncherActivityInfo info : matches) { - AppInfo applicationInfo = findApplicationInfoLocked( - info.getComponentName().getPackageName(), user, - info.getComponentName().getClassName()); + AppInfo applicationInfo = findAppInfo(info.getComponentName(), user); if (applicationInfo == null) { add(new AppInfo(context, info, user), info); } else { @@ -208,28 +232,14 @@ public class AllAppsList { } /** - * Returns whether <em>apps</em> contains <em>component</em>. - */ - private static boolean findActivity(ArrayList<AppInfo> apps, ComponentName component, - UserHandle user) { - final int N = apps.size(); - for (int i = 0; i < N; i++) { - final AppInfo info = apps.get(i); - if (info.user.equals(user) && info.componentName.equals(component)) { - return true; - } - } - return false; - } - - /** - * Find an ApplicationInfo object for the given packageName and className. + * Find an AppInfo object for the given componentName + * + * @return the corresponding AppInfo or null */ - private AppInfo findApplicationInfoLocked(String packageName, UserHandle user, - String className) { + private @Nullable AppInfo findAppInfo(@NonNull ComponentName componentName, + @NonNull UserHandle user) { for (AppInfo info: data) { - if (user.equals(info.user) && packageName.equals(info.componentName.getPackageName()) - && className.equals(info.componentName.getClassName())) { + if (componentName.equals(info.componentName) && user.equals(info.user)) { return info; } } diff --git a/src/com/android/launcher3/AppFilter.java b/src/com/android/launcher3/AppFilter.java index db8f5dd0e..923835a67 100644 --- a/src/com/android/launcher3/AppFilter.java +++ b/src/com/android/launcher3/AppFilter.java @@ -1,9 +1,14 @@ package com.android.launcher3; import android.content.ComponentName; +import android.content.Context; public class AppFilter { + public static AppFilter newInstance(Context context) { + return Utilities.getOverrideObject(AppFilter.class, context, R.string.app_filter_class); + } + public boolean shouldShowApp(ComponentName app) { return true; } diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java index 84a8bce6e..70be7dae4 100644 --- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java +++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java @@ -12,6 +12,7 @@ import android.os.Handler; import android.util.Log; import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.model.LoaderTask; import com.android.launcher3.util.ContentWriter; public class AppWidgetsRestoredReceiver extends BroadcastReceiver { @@ -52,7 +53,7 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver { final AppWidgetProviderInfo provider = widgets.getAppWidgetInfo(newWidgetIds[i]); final int state; - if (LauncherModel.isValidProvider(provider)) { + if (LoaderTask.isValidProvider(provider)) { // This will ensure that we show 'Click to setup' UI if required. state = LauncherAppWidgetInfo.FLAG_UI_NOT_READY; } else { diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index c4086a89d..98eb208eb 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -393,7 +393,7 @@ public class AutoInstallsLayout { return -1; } - mValues.put(Favorites.RESTORED, ShortcutInfo.FLAG_AUTOINTALL_ICON); + mValues.put(Favorites.RESTORED, ShortcutInfo.FLAG_AUTOINSTALL_ICON); final Intent intent = new Intent(Intent.ACTION_MAIN, null) .addCategory(Intent.CATEGORY_LAUNCHER) .setComponent(new ComponentName(packageName, className)) diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java index 6fdf45450..514cc0751 100644 --- a/src/com/android/launcher3/BaseRecyclerView.java +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -48,6 +48,8 @@ public abstract class BaseRecyclerView extends RecyclerView private int mDownY; private int mLastY; + private boolean mScrollBarVisible = true; + public BaseRecyclerView(Context context) { this(context, null); } @@ -82,10 +84,6 @@ public abstract class BaseRecyclerView extends RecyclerView } } - public void reset() { - mScrollbar.reattachThumbToScroll(); - } - @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -117,6 +115,7 @@ public abstract class BaseRecyclerView extends RecyclerView * it is already showing). */ private boolean handleTouchEvent(MotionEvent ev) { + ev.offsetLocation(0, -getPaddingTop()); int action = ev.getAction(); int x = (int) ev.getX(); int y = (int) ev.getY(); @@ -140,6 +139,7 @@ public abstract class BaseRecyclerView extends RecyclerView mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY); break; } + ev.offsetLocation(0, getPaddingTop()); return mScrollbar.isDraggingThumb(); } @@ -166,7 +166,7 @@ public abstract class BaseRecyclerView extends RecyclerView * Returns the height of the fast scroll bar */ protected int getScrollbarTrackHeight() { - return getHeight(); + return getHeight() - getPaddingTop() - getPaddingBottom(); } /** @@ -201,8 +201,18 @@ public abstract class BaseRecyclerView extends RecyclerView @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); - onUpdateScrollbar(0); - mScrollbar.draw(canvas); + if (mScrollBarVisible) { + onUpdateScrollbar(0); + mScrollbar.draw(canvas); + } + } + + /** + * Sets the scrollbar visibility. The call does not refresh the UI, its the responsibility + * of the caller to call {@link #invalidate()}. + */ + public void setScrollBarVisible(boolean visible) { + mScrollBarVisible = visible; } /** diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java index 5feb42ea8..303974464 100644 --- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java +++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java @@ -136,6 +136,7 @@ public class BaseRecyclerViewFastScrollBar { mTmpRect.set(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight); mThumbOffsetY = y; mTmpRect.union(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight); + mTmpRect.offset(0, mRv.getPaddingTop()); mRv.invalidate(mTmpRect); } @@ -148,8 +149,9 @@ public class BaseRecyclerViewFastScrollBar { return; } int left = getDrawLeft(); + int top = mRv.getPaddingTop(); // Invalidate the whole scroll bar area. - mRv.invalidate(left, 0, left + mMaxWidth, mRv.getScrollbarTrackHeight()); + mRv.invalidate(left, top, left + mMaxWidth, top + mRv.getScrollbarTrackHeight()); mWidth = width; updateThumbPath(); @@ -265,6 +267,7 @@ public class BaseRecyclerViewFastScrollBar { if (!mIsRtl) { canvas.translate(mRv.getWidth(), 0); } + canvas.translate(0, mRv.getPaddingTop()); // Draw the track int thumbWidth = mIsRtl ? mWidth : -mWidth; canvas.drawRect(0, 0, thumbWidth, mRv.getScrollbarTrackHeight(), mTrackPaint); diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 3c3e224e4..b0e8b1e81 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -23,10 +23,12 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Property; @@ -43,12 +45,13 @@ import com.android.launcher3.IconCache.IconLoadRequest; 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.util.Themes; import java.text.NumberFormat; @@ -63,8 +66,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { private static final float AMBIENT_SHADOW_RADIUS = 2.5f; private static final float KEY_SHADOW_RADIUS = 1f; private static final float KEY_SHADOW_OFFSET = 0.5f; - private static final int AMBIENT_SHADOW_COLOR = 0x33000000; - private static final int KEY_SHADOW_COLOR = 0x66000000; private static final int DISPLAY_WORKSPACE = 0; private static final int DISPLAY_ALL_APPS = 1; @@ -80,6 +81,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { private final CheckLongPressHelper mLongPressHelper; private final HolographicOutlineHelper mOutlineHelper; private final StylusEventHelper mStylusEventHelper; + private final int mAmbientShadowColor; + private final int mKeyShadowColor; private boolean mBackgroundSizeChanged; @@ -93,6 +96,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { private final int mIconSize; @ViewDebug.ExportedProperty(category = "launcher") private int mTextColor; + private boolean mIsIconVisible = true; private BadgeInfo mBadgeInfo; private BadgeRenderer mBadgeRenderer; @@ -144,6 +148,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false); mDeferShadowGenerationOnTouch = a.getBoolean(R.styleable.BubbleTextView_deferShadowGeneration, false); + mAmbientShadowColor = a.getColor(R.styleable.BubbleTextView_ambientShadowColor, 0x33000000); + mKeyShadowColor = a.getColor(R.styleable.BubbleTextView_keyShadowColor, 0x66000000); int display = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE); int defaultIconSize = grid.iconSizePx; @@ -171,7 +177,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { // Set shadow layer as the larger shadow to that the textView does not clip the shadow. float density = getResources().getDisplayMetrics().density; - setShadowLayer(density * AMBIENT_SHADOW_RADIUS, 0, 0, AMBIENT_SHADOW_COLOR); + setShadowLayer(density * AMBIENT_SHADOW_RADIUS, 0, 0, mAmbientShadowColor); } else { mBackground = null; } @@ -206,6 +212,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { // Verify high res immediately verifyHighRes(); + if (info instanceof PromiseAppInfo) { + PromiseAppInfo promiseAppInfo = (PromiseAppInfo) info; + applyProgressLevel(promiseAppInfo.level); + } applyBadgeState(info, false /* animate */); } @@ -428,14 +438,14 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { // We enhance the shadow by drawing the shadow twice float density = getResources().getDisplayMetrics().density; - getPaint().setShadowLayer(density * AMBIENT_SHADOW_RADIUS, 0, 0, AMBIENT_SHADOW_COLOR); + getPaint().setShadowLayer(density * AMBIENT_SHADOW_RADIUS, 0, 0, mAmbientShadowColor); super.draw(canvas); canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(), getScrollX() + getWidth(), getScrollY() + getHeight(), Region.Op.INTERSECT); getPaint().setShadowLayer( - density * KEY_SHADOW_RADIUS, 0.0f, density * KEY_SHADOW_OFFSET, KEY_SHADOW_COLOR); + density * KEY_SHADOW_RADIUS, 0.0f, density * KEY_SHADOW_OFFSET, mKeyShadowColor); super.draw(canvas); canvas.restore(); @@ -547,27 +557,36 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { ((info.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE) ? info.getInstallProgress() : 0)) : 100; - setContentDescription(progressLevel > 0 ? - getContext().getString(R.string.app_downloading_title, info.title, - NumberFormat.getPercentInstance().format(progressLevel * 0.01)) : - getContext().getString(R.string.app_waiting_download_title, info.title)); + PreloadIconDrawable preloadDrawable = applyProgressLevel(progressLevel); + if (preloadDrawable != null && promiseStateChanged) { + preloadDrawable.maybePerformFinishedAnimation(); + } + } + } + + public PreloadIconDrawable applyProgressLevel(int progressLevel) { + if (getTag() instanceof ItemInfoWithIcon) { + ItemInfoWithIcon info = (ItemInfoWithIcon) getTag(); + setContentDescription(progressLevel > 0 + ? getContext().getString(R.string.app_downloading_title, info.title, + NumberFormat.getPercentInstance().format(progressLevel * 0.01)) + : getContext().getString(R.string.app_waiting_download_title, info.title)); if (mIcon != null) { final PreloadIconDrawable preloadDrawable; if (mIcon instanceof PreloadIconDrawable) { preloadDrawable = (PreloadIconDrawable) mIcon; + preloadDrawable.setLevel(progressLevel); } else { preloadDrawable = DrawableFactory.get(getContext()) .newPendingIcon(info.iconBitmap, getContext()); + preloadDrawable.setLevel(progressLevel); setIcon(preloadDrawable); } - - preloadDrawable.setLevel(progressLevel); - if (promiseStateChanged) { - preloadDrawable.maybePerformFinishedAnimation(); - } + return preloadDrawable; } } + return null; } public void applyBadgeState(ItemInfo itemInfo, boolean animate) { @@ -603,7 +622,21 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { private void setIcon(Drawable icon) { mIcon = icon; mIcon.setBounds(0, 0, mIconSize, mIconSize); - applyCompoundDrawables(mIcon); + if (mIsIconVisible) { + applyCompoundDrawables(mIcon); + } + } + + public void setIconVisible(boolean visible) { + mIsIconVisible = visible; + mDisableRelayout = true; + Drawable icon = mIcon; + if (!visible) { + icon = new ColorDrawable(Color.TRANSPARENT); + icon.setBounds(0, 0, mIconSize, mIconSize); + } + applyCompoundDrawables(icon); + mDisableRelayout = false; } protected void applyCompoundDrawables(Drawable icon) { @@ -634,7 +667,9 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { 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) { @@ -666,6 +701,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { } } + public int getIconSize() { + return mIconSize; + } + /** * Interface to be implemented by the grand parent to allow click shadow effect. */ diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 8179dad29..c2c5c27db 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -51,12 +51,13 @@ 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; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.ParcelableSparseArray; +import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; import java.lang.annotation.Retention; @@ -235,7 +236,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { for (int i = 0; i < mDragOutlines.length; i++) { mDragOutlines[i] = new Rect(-1, -1, -1, -1); } - mDragOutlinePaint.setColor(getResources().getColor(R.color.outline_color)); + mDragOutlinePaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor)); // When dragging things around the home screens, we show a green outline of // where the item will land. The outlines gradually fade out, leaving a trail @@ -541,11 +542,8 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { } public void setFolderLeaveBehindCell(int x, int y) { - - DeviceProfile grid = mLauncher.getDeviceProfile(); View child = getChildAt(x, y); - - mFolderLeaveBehind.setup(getResources().getDisplayMetrics(), grid, null, + mFolderLeaveBehind.setup(mLauncher, null, child.getMeasuredWidth(), child.getPaddingTop()); mFolderLeaveBehind.delegateCellX = x; @@ -568,7 +566,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/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java index 9097ed23d..975675a6f 100644 --- a/src/com/android/launcher3/DeleteDropTarget.java +++ b/src/com/android/launcher3/DeleteDropTarget.java @@ -40,7 +40,7 @@ public class DeleteDropTarget extends ButtonDropTarget { // Get the hover color mHoverColor = getResources().getColor(R.color.delete_target_hover_tint); - setDrawable(R.drawable.ic_remove_launcher); + setDrawable(R.drawable.ic_remove_shadow); } @Override diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index e47031ae8..9bb56d603 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -113,9 +113,9 @@ public class DeviceProfile { public int hotseatCellHeightPx; public int hotseatIconSizePx; public int hotseatBarHeightPx; - private int hotseatBarTopPaddingPx; - private int hotseatBarBottomPaddingPx; - private int hotseatLandGutterPx; + public int hotseatBarTopPaddingPx; + public int hotseatBarBottomPaddingPx; + public int hotseatLandGutterPx; // All apps public int allAppsNumCols; @@ -191,11 +191,14 @@ public class DeviceProfile { dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size); workspaceSpringLoadedBottomSpace = res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space); - hotseatBarHeightPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_height); + hotseatBarTopPaddingPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding); - hotseatBarBottomPaddingPx = 0; - hotseatLandGutterPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_gutter_width); + hotseatBarBottomPaddingPx = + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding); + hotseatBarHeightPx = hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx + + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_height); + hotseatLandGutterPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_land_gutter_width); // Determine sizes. widthPx = width; @@ -228,9 +231,6 @@ public class DeviceProfile { profile.cellHeightPx = profile.iconSizePx + profile.iconDrawablePaddingPx + Utilities.calculateTextHeight(profile.iconTextSizePx); - // The nav bar is black so we add bottom padding to visually center hotseat icons. - profile.hotseatBarBottomPaddingPx = profile.hotseatBarTopPaddingPx; - // We use these scales to measure and layout the widgets using their full invariant profile // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans. float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x; @@ -532,14 +532,6 @@ public class DeviceProfile { workspacePadding.bottom); workspace.setPageSpacing(getWorkspacePageSpacing()); - // 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); lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams(); 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 0041bb4d6..21254ab29 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -65,9 +65,17 @@ public class FolderInfo extends ItemInfo { * @param item */ public void add(ShortcutInfo item, boolean animate) { - contents.add(item); + add(item, contents.size(), animate); + } + + /** + * Add an app or shortcut for a specified rank. + */ + public void add(ShortcutInfo item, int rank, boolean animate) { + rank = Utilities.boundToRange(rank, 0, contents.size()); + contents.add(rank, item); for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onAdd(item); + listeners.get(i).onAdd(item, rank); } itemsChanged(animate); } @@ -121,7 +129,7 @@ public class FolderInfo extends ItemInfo { } public interface FolderListener { - public void onAdd(ShortcutInfo item); + public void onAdd(ShortcutInfo item, int rank); public void onRemove(ShortcutInfo item); public void onTitleChanged(CharSequence title); public void onItemsChanged(boolean animate); diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java index 47052a77e..ab82c988e 100644 --- a/src/com/android/launcher3/Hotseat.java +++ b/src/com/android/launcher3/Hotseat.java @@ -72,7 +72,9 @@ public class Hotseat extends FrameLayout mBackgroundColor = ColorUtils.setAlphaComponent( Themes.getAttrColor(context, android.R.attr.colorPrimary), 0); mBackground = new ColorDrawable(mBackgroundColor); - setBackground(mBackground); + if (!FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { + setBackground(mBackground); + } } public CellLayout getLayout() { @@ -179,8 +181,12 @@ public class Hotseat extends FrameLayout } public void updateColor(ExtractedColors extractedColors, boolean animate) { + if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { + // not hotseat visible + return; + } if (!mHasVerticalHotseat) { - int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX, Color.TRANSPARENT); + int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX); if (mBackgroundColorAnimator != null) { mBackgroundColorAnimator.cancel(); } diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index 12170309b..ad816afbc 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -32,10 +32,6 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Handler; @@ -55,7 +51,6 @@ import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Provider; import com.android.launcher3.util.SQLiteCacheHelper; -import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; import java.util.Collections; @@ -100,24 +95,14 @@ public class IconCache { @Thunk final UserManagerCompat mUserManager; private final LauncherAppsCompat mLauncherApps; private final HashMap<ComponentKey, CacheEntry> mCache = - new HashMap<ComponentKey, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY); + new HashMap<>(INITIAL_ICON_CACHE_CAPACITY); private final int mIconDpi; @Thunk final IconDB mIconDb; @Thunk final Handler mWorkerHandler; - // The background color used for activity icons. Since these icons are displayed in all-apps - // and folders, this would be same as the light quantum panel background. This color - // is used to convert icons to RGB_565. - private final int mActivityBgColor; - // The background color used for package icons. These are displayed in widget tray, which - // has a dark quantum panel background. - private final int mPackageBgColor; private final BitmapFactory.Options mLowResOptions; - private Canvas mLowResCanvas; - private Paint mLowResPaint; - public IconCache(Context context, InvariantDeviceProfile inv) { mContext = context; mPackageManager = context.getPackageManager(); @@ -125,16 +110,11 @@ public class IconCache { mLauncherApps = LauncherAppsCompat.getInstance(mContext); mIconDpi = inv.fillResIconDpi; mIconDb = new IconDB(context, inv.iconBitmapSize); - mLowResCanvas = new Canvas(); - mLowResPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG); mIconProvider = Utilities.getOverrideObject( IconProvider.class, context, R.string.icon_provider_class); mWorkerHandler = new Handler(LauncherModel.getWorkerLooper()); - mActivityBgColor = Themes.getColorPrimary(context, R.style.LauncherTheme); - mPackageBgColor = Themes.getColorPrimary(context, R.style.WidgetContainerTheme); - mLowResOptions = new BitmapFactory.Options(); // Always prefer RGB_565 config for low res. If the bitmap has transparency, it will // automatically be loaded as ALPHA_8888. @@ -387,7 +367,7 @@ public class IconCache { entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser()); mCache.put(key, entry); - Bitmap lowResIcon = generateLowResIcon(entry.icon, mActivityBgColor); + Bitmap lowResIcon = generateLowResIcon(entry.icon); ContentValues values = newContentValues(entry.icon, lowResIcon, entry.title.toString(), app.getApplicationInfo().packageName); addIconToDB(values, app.getComponentName(), info, userSerial); @@ -637,7 +617,7 @@ public class IconCache { // only keep the low resolution icon instead of the larger full-sized icon Bitmap icon = LauncherIcons.createBadgedIconBitmap( appInfo.loadIcon(mPackageManager), user, mContext, appInfo.targetSdkVersion); - Bitmap lowResIcon = generateLowResIcon(icon, mPackageBgColor); + Bitmap lowResIcon = generateLowResIcon(icon); entry.title = appInfo.loadLabel(mPackageManager); entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); entry.icon = useLowResIcon ? lowResIcon : icon; @@ -769,7 +749,7 @@ public class IconCache { } private static final class IconDB extends SQLiteCacheHelper { - private final static int DB_VERSION = 13; + private final static int DB_VERSION = 14; private final static int RELEASE_VERSION = DB_VERSION + (FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? 0 : 1); @@ -822,24 +802,10 @@ public class IconCache { /** * Generates a new low-res icon given a high-res icon. */ - private Bitmap generateLowResIcon(Bitmap icon, int lowResBackgroundColor) { - if (lowResBackgroundColor == Color.TRANSPARENT) { - return Bitmap.createScaledBitmap(icon, - icon.getWidth() / LOW_RES_SCALE_FACTOR, - icon.getHeight() / LOW_RES_SCALE_FACTOR, true); - } else { - Bitmap lowResIcon = Bitmap.createBitmap(icon.getWidth() / LOW_RES_SCALE_FACTOR, - icon.getHeight() / LOW_RES_SCALE_FACTOR, Bitmap.Config.RGB_565); - synchronized (this) { - mLowResCanvas.setBitmap(lowResIcon); - mLowResCanvas.drawColor(lowResBackgroundColor); - mLowResCanvas.drawBitmap(icon, new Rect(0, 0, icon.getWidth(), icon.getHeight()), - new Rect(0, 0, lowResIcon.getWidth(), lowResIcon.getHeight()), - mLowResPaint); - mLowResCanvas.setBitmap(null); - } - return lowResIcon; - } + private Bitmap generateLowResIcon(Bitmap icon) { + return Bitmap.createScaledBitmap(icon, + icon.getWidth() / LOW_RES_SCALE_FACTOR, + icon.getHeight() / LOW_RES_SCALE_FACTOR, true); } private static Bitmap loadIconNoResize(Cursor c, int iconIndex, BitmapFactory.Options options) { diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java index 0608fdd2e..f088d1176 100644 --- a/src/com/android/launcher3/InfoDropTarget.java +++ b/src/com/android/launcher3/InfoDropTarget.java @@ -42,12 +42,10 @@ public class InfoDropTarget extends UninstallDropTarget { } @Override - protected void onFinishInflate() { - super.onFinishInflate(); + protected void setupUi() { // Get the hover color mHoverColor = Themes.getColorAccent(getContext()); - - setDrawable(R.drawable.ic_info_launcher); + setDrawable(R.drawable.ic_info_shadow); } @Override @@ -67,6 +65,11 @@ public class InfoDropTarget extends UninstallDropTarget { public static boolean startDetailsActivityForInfo(ItemInfo info, Launcher launcher, DropTargetResultCallback callback, Rect sourceBounds, Bundle opts) { + if (info instanceof PromiseAppInfo) { + PromiseAppInfo promiseAppInfo = (PromiseAppInfo) info; + launcher.startActivity(promiseAppInfo.getMarketIntent()); + return true; + } boolean result = false; ComponentName componentName = null; if (info instanceof AppInfo) { diff --git a/src/com/android/launcher3/InsettableFrameLayout.java b/src/com/android/launcher3/InsettableFrameLayout.java index 154641cab..be7649013 100644 --- a/src/com/android/launcher3/InsettableFrameLayout.java +++ b/src/com/android/launcher3/InsettableFrameLayout.java @@ -9,9 +9,6 @@ import android.view.ViewDebug; import android.view.ViewGroup; import android.widget.FrameLayout; -import com.android.launcher3.allapps.AllAppsContainerView; -import com.android.launcher3.config.FeatureFlags; - public class InsettableFrameLayout extends FrameLayout implements ViewGroup.OnHierarchyChangeListener, Insettable { diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index ce8557065..b136e7d81 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -34,6 +34,7 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Base64; import android.util.Log; +import android.util.Pair; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; @@ -59,6 +60,16 @@ import java.util.List; import java.util.Set; public class InstallShortcutReceiver extends BroadcastReceiver { + + public static final int FLAG_ACTIVITY_PAUSED = 1; + public static final int FLAG_LOADER_RUNNING = 2; + public static final int FLAG_DRAG_AND_DROP = 4; + public static final int FLAG_BULK_ADD = 4; + + // Determines whether to defer installing shortcuts immediately until + // processAllPendingInstalls() is called. + private static int sInstallQueueDisabledFlags = 0; + private static final String TAG = "InstallShortcutReceiver"; private static final boolean DBG = false; @@ -151,10 +162,6 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } } - // Determines whether to defer installing shortcuts immediately until - // processAllPendingInstalls() is called. - private static boolean mUseInstallQueue = false; - public void onReceive(Context context, Intent data) { if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) { return; @@ -207,7 +214,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { public static ShortcutInfo fromShortcutIntent(Context context, Intent data) { PendingInstallShortcutInfo info = createPendingInfo(context, data); - return info == null ? null : (ShortcutInfo) info.getItemInfo(); + return info == null ? null : (ShortcutInfo) info.getItemInfo().first; } public static void queueShortcut(ShortcutInfoCompat info, Context context) { @@ -245,27 +252,28 @@ public class InstallShortcutReceiver extends BroadcastReceiver { private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) { // Queue the item up for adding if launcher has not loaded properly yet - LauncherAppState app = LauncherAppState.getInstance(context); - boolean launcherNotLoaded = app.getModel().getCallback() == null; - addToInstallQueue(Utilities.getPrefs(context), info); - if (!mUseInstallQueue && !launcherNotLoaded) { - flushInstallQueue(context); - } + flushInstallQueue(context); } - static void enableInstallQueue() { - mUseInstallQueue = true; + public static void enableInstallQueue(int flag) { + sInstallQueueDisabledFlags |= flag; } - static void disableAndFlushInstallQueue(Context context) { - mUseInstallQueue = false; + public static void disableAndFlushInstallQueue(int flag, Context context) { + sInstallQueueDisabledFlags &= ~flag; flushInstallQueue(context); } static void flushInstallQueue(Context context) { + LauncherModel model = LauncherAppState.getInstance(context).getModel(); + boolean launcherNotLoaded = model.getCallback() == null; + if (sInstallQueueDisabledFlags != 0 || launcherNotLoaded) { + return; + } + ArrayList<PendingInstallShortcutInfo> items = getAndClearInstallQueue(context); if (!items.isEmpty()) { - LauncherAppState.getInstance(context).getModel().addAndBindAddedWorkspaceItems( + model.addAndBindAddedWorkspaceItems( new LazyShortcutsProvider(context.getApplicationContext(), items)); } } @@ -439,7 +447,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } } - public ItemInfo getItemInfo() { + public Pair<ItemInfo, Object> getItemInfo() { if (activityInfo != null) { AppInfo appInfo = new AppInfo(mContext, activityInfo, user); final LauncherAppState app = LauncherAppState.getInstance(mContext); @@ -459,11 +467,11 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } }); } - return si; + return Pair.create((ItemInfo) si, (Object) activityInfo); } else if (shortcutInfo != null) { ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext); si.iconBitmap = LauncherIcons.createShortcutIcon(shortcutInfo, mContext); - return si; + return Pair.create((ItemInfo) si, (Object) shortcutInfo); } else if (providerInfo != null) { LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo .fromProviderInfo(mContext, providerInfo); @@ -475,9 +483,10 @@ public class InstallShortcutReceiver extends BroadcastReceiver { widgetInfo.minSpanY = info.minSpanY; widgetInfo.spanX = Math.min(info.spanX, idp.numColumns); widgetInfo.spanY = Math.min(info.spanY, idp.numRows); - return widgetInfo; + return Pair.create((ItemInfo) widgetInfo, (Object) providerInfo); } else { - return createShortcutInfo(data, LauncherAppState.getInstance(mContext)); + ShortcutInfo si = createShortcutInfo(data, LauncherAppState.getInstance(mContext)); + return Pair.create((ItemInfo) si, null); } } @@ -588,7 +597,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { return new PendingInstallShortcutInfo(info, original.mContext); } - private static class LazyShortcutsProvider extends Provider<List<ItemInfo>> { + private static class LazyShortcutsProvider extends Provider<List<Pair<ItemInfo, Object>>> { private final Context mContext; private final ArrayList<PendingInstallShortcutInfo> mPendingItems; @@ -603,9 +612,9 @@ public class InstallShortcutReceiver extends BroadcastReceiver { * packageManager and icon cache. */ @Override - public ArrayList<ItemInfo> get() { + public ArrayList<Pair<ItemInfo, Object>> get() { Preconditions.assertNonUiThread(); - ArrayList<ItemInfo> installQueue = new ArrayList<>(); + ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>(); LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext); for (PendingInstallShortcutInfo pendingInfo : mPendingItems) { // If the intent specifies a package, make sure the package exists diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 9e214d1ec..d22461535 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.util.Thunk; import org.xmlpull.v1.XmlPullParser; @@ -317,7 +316,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/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java index 0779a3d20..11c5309f2 100644 --- a/src/com/android/launcher3/ItemInfo.java +++ b/src/com/android/launcher3/ItemInfo.java @@ -39,8 +39,10 @@ public class ItemInfo { /** * One of {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}, * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_FOLDER}, or - * {@link LauncherSettings.Favorites#ITEM_TYPE_APPWIDGET}. + * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT} + * {@link LauncherSettings.Favorites#ITEM_TYPE_FOLDER}, + * {@link LauncherSettings.Favorites#ITEM_TYPE_APPWIDGET} or + * {@link LauncherSettings.Favorites#ITEM_TYPE_CUSTOM_APPWIDGET}. */ public int itemType; @@ -53,7 +55,9 @@ public class ItemInfo { public long container = NO_ID; /** - * Indicates the screen in which the shortcut appears. + * Indicates the screen in which the shortcut appears if the container types is + * {@link LauncherSettings.Favorites#CONTAINER_DESKTOP}. (i.e., ignore if the container type is + * {@link LauncherSettings.Favorites#CONTAINER_HOTSEAT}) */ public long screenId = -1; @@ -178,15 +182,12 @@ public class ItemInfo { protected String dumpProperties() { return "id=" + id - + " type=" + itemType - + " container=" + container + + " type=" + LauncherSettings.Favorites.itemTypeToString(itemType) + + " container=" + LauncherSettings.Favorites.containerToString((int)container) + " screen=" + screenId - + " cellX=" + cellX - + " cellY=" + cellY - + " spanX=" + spanX - + " spanY=" + spanY - + " minSpanX=" + minSpanX - + " minSpanY=" + minSpanY + + " cell(" + cellX + "," + cellY + ")" + + " span(" + spanX + "," + spanY + ")" + + " minSpan(" + minSpanX + "," + minSpanY + ")" + " rank=" + rank + " user=" + user + " title=" + title; diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 2de10030d..62cf2b3c4 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -18,7 +18,9 @@ package com.android.launcher3; import android.Manifest; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.annotation.TargetApi; @@ -65,6 +67,7 @@ import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.KeyboardShortcutGroup; import android.view.KeyboardShortcutInfo; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MotionEvent; import android.view.View; @@ -81,22 +84,22 @@ import android.widget.Toast; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; 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.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompat; -import com.android.launcher3.compat.PinItemRequestCompat; +import com.android.launcher3.compat.LauncherAppsCompatVO; 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; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dragndrop.PinItemDragListener; import com.android.launcher3.dynamicui.ExtractedColors; +import com.android.launcher3.dynamicui.WallpaperColorInfo; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.keyboard.CustomActionsPopup; @@ -124,6 +127,7 @@ import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.PendingRequestArgs; import com.android.launcher3.util.TestingUtils; +import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.ViewOnDrawExecutor; import com.android.launcher3.widget.PendingAddShortcutInfo; @@ -147,7 +151,8 @@ import java.util.Set; public class Launcher extends BaseActivity implements LauncherExterns, View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener, - AccessibilityManager.AccessibilityStateChangeListener { + AccessibilityManager.AccessibilityStateChangeListener, + WallpaperColorInfo.OnThemeChangeListener { public static final String TAG = "Launcher"; static final boolean LOGD = false; @@ -157,14 +162,15 @@ public class Launcher extends BaseActivity private static final int REQUEST_CREATE_SHORTCUT = 1; private static final int REQUEST_CREATE_APPWIDGET = 5; + private static final int REQUEST_PICK_APPWIDGET = 9; private static final int REQUEST_PICK_WALLPAPER = 10; private static final int REQUEST_BIND_APPWIDGET = 11; - private static final int REQUEST_BIND_PENDING_APPWIDGET = 14; - private static final int REQUEST_RECONFIGURE_APPWIDGET = 12; + private static final int REQUEST_BIND_PENDING_APPWIDGET = 12; + private static final int REQUEST_RECONFIGURE_APPWIDGET = 13; - private static final int REQUEST_PERMISSION_CALL_PHONE = 13; + private static final int REQUEST_PERMISSION_CALL_PHONE = 14; private static final float BOUNCE_ANIMATION_TENSION = 1.3f; @@ -213,11 +219,12 @@ public class Launcher extends BaseActivity private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5; @Thunk static int NEW_APPS_ANIMATION_DELAY = 500; + private final ExtractedColors mExtractedColors = new ExtractedColors(); + @Thunk Workspace mWorkspace; private View mLauncherView; @Thunk DragLayer mDragLayer; private DragController mDragController; - private View mQsbContainer; public View mWeightWatcher; @@ -261,13 +268,14 @@ public class Launcher extends BaseActivity private LauncherModel mModel; private ModelWriter mModelWriter; private IconCache mIconCache; - private ExtractedColors mExtractedColors; private LauncherAccessibilityDelegate mAccessibilityDelegate; private Handler mHandler = new Handler(); private boolean mIsResumeFromActionScreenOff; private boolean mHasFocus = false; private boolean mAttached = false; + private ObjectAnimator mScrimAnimator; + private PopupDataProvider mPopupDataProvider; private View.OnTouchListener mHapticFeedbackTouchListener; @@ -359,6 +367,10 @@ public class Launcher extends BaseActivity mLauncherCallbacks.preOnCreate(); } + WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.getInstance(this); + wallpaperColorInfo.setOnThemeChangeListener(this); + overrideTheme(wallpaperColorInfo.isDark(), wallpaperColorInfo.supportsDarkText()); + super.onCreate(savedInstanceState); LauncherAppState app = LauncherAppState.getInstance(this); @@ -393,11 +405,10 @@ public class Launcher extends BaseActivity // LauncherModel load. mPaused = false; - mLauncherView = getLayoutInflater().inflate(R.layout.launcher, null); + mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null); setupViews(); mDeviceProfile.layout(this, false /* notifyListeners */); - mExtractedColors = new ExtractedColors(); loadExtractedColorsAndColorItems(); mPopupDataProvider = new PopupDataProvider(this); @@ -456,6 +467,23 @@ public class Launcher extends BaseActivity if (mLauncherCallbacks != null) { mLauncherCallbacks.onCreate(savedInstanceState); } + + if (Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText)) { + activateLightSystemBars(true, true, true); + } + } + + @Override + public void onThemeChanged() { + recreate(); + } + + protected void overrideTheme(boolean isDark, boolean supportsDarkText) { + if (isDark) { + setTheme(R.style.LauncherThemeDark); + } else if (supportsDarkText) { + setTheme(R.style.LauncherThemeDarkText); + } } @Override @@ -466,6 +494,11 @@ public class Launcher extends BaseActivity @Override public void onExtractedColorsChanged() { loadExtractedColorsAndColorItems(); + mExtractedColors.notifyChange(); + } + + public ExtractedColors getExtractedColors() { + return mExtractedColors; } @Override @@ -481,19 +514,9 @@ public class Launcher extends BaseActivity mExtractedColors.load(this); mHotseat.updateColor(mExtractedColors, !mPaused); mWorkspace.getPageIndicator().updateColor(mExtractedColors); - boolean lightStatusBar = (FeatureFlags.LIGHT_STATUS_BAR - && mExtractedColors.getColor(ExtractedColors.STATUS_BAR_INDEX, - ExtractedColors.DEFAULT_DARK) == ExtractedColors.DEFAULT_LIGHT); - // It's possible that All Apps is visible when this is run, - // so always use light status bar in that case. Only change nav bar color to status bar - // color when All Apps is visible. - activateLightSystemBars(lightStatusBar || isAllAppsVisible(), true, isAllAppsVisible()); } } - // TODO: use platform flag on API >= 26 - private static final int SYSTEM_UI_FLAG_LIGHT_NAV_BAR = 0x10; - /** * Sets the status and/or nav bar to be light or not. Light status bar means dark icons. * @param isLight make sure the system bar is light. @@ -508,14 +531,14 @@ public class Launcher extends BaseActivity newSystemUiFlags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; } if (navBar && Utilities.isAtLeastO()) { - newSystemUiFlags |= SYSTEM_UI_FLAG_LIGHT_NAV_BAR; + newSystemUiFlags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; } } else { if (statusBar) { newSystemUiFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } if (navBar && Utilities.isAtLeastO()) { - newSystemUiFlags &= ~(SYSTEM_UI_FLAG_LIGHT_NAV_BAR); + newSystemUiFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); } } @@ -550,47 +573,6 @@ public class Launcher extends BaseActivity public boolean setLauncherCallbacks(LauncherCallbacks callbacks) { mLauncherCallbacks = callbacks; - mLauncherCallbacks.setLauncherSearchCallback(new Launcher.LauncherSearchCallbacks() { - private boolean mWorkspaceImportanceStored = false; - private boolean mHotseatImportanceStored = false; - private int mWorkspaceImportanceForAccessibility = - View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; - private int mHotseatImportanceForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; - - @Override - public void onSearchOverlayOpened() { - if (mWorkspaceImportanceStored || mHotseatImportanceStored) { - return; - } - // The underlying workspace and hotseat are temporarily suppressed by the search - // overlay. So they shouldn't be accessible. - if (mWorkspace != null) { - mWorkspaceImportanceForAccessibility = - mWorkspace.getImportantForAccessibility(); - mWorkspace.setImportantForAccessibility( - View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - mWorkspaceImportanceStored = true; - } - if (mHotseat != null) { - mHotseatImportanceForAccessibility = mHotseat.getImportantForAccessibility(); - mHotseat.setImportantForAccessibility( - View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - mHotseatImportanceStored = true; - } - } - - @Override - public void onSearchOverlayClosed() { - if (mWorkspaceImportanceStored && mWorkspace != null) { - mWorkspace.setImportantForAccessibility(mWorkspaceImportanceForAccessibility); - } - if (mHotseatImportanceStored && mHotseat != null) { - mHotseat.setImportantForAccessibility(mHotseatImportanceForAccessibility); - } - mWorkspaceImportanceStored = false; - mHotseatImportanceStored = false; - } - }); return true; } @@ -952,6 +934,24 @@ public class Launcher extends BaseActivity if (!isWorkspaceLoading()) { NotificationListener.setNotificationsChangedListener(mPopupDataProvider); } + + if (mIsResumeFromActionScreenOff && mDragLayer.getBackground() != null) { + if (mScrimAnimator != null) { + mScrimAnimator.cancel(); + } + mDragLayer.getBackground().setAlpha(0); + mScrimAnimator = ObjectAnimator.ofInt(mDragLayer.getBackground(), + LauncherAnimUtils.DRAWABLE_ALPHA, 0, 255); + mScrimAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mScrimAnimator = null; + } + }); + mScrimAnimator.setDuration(600); + mScrimAnimator.setStartDelay(getWindow().getTransitionBackgroundFadeDuration()); + mScrimAnimator.start(); + } } @Override @@ -1051,13 +1051,12 @@ public class Launcher extends BaseActivity updateInteraction(Workspace.State.NORMAL, mWorkspace.getState()); mWorkspace.onResume(); - if (!isWorkspaceLoading()) { - // Process any items that were added while Launcher was away. - InstallShortcutReceiver.disableAndFlushInstallQueue(this); + // Process any items that were added while Launcher was away. + InstallShortcutReceiver.disableAndFlushInstallQueue( + InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this); - // Refresh shortcuts if the permission changed. - mModel.refreshShortcutsIfRequired(); - } + // Refresh shortcuts if the permission changed. + mModel.refreshShortcutsIfRequired(); if (shouldShowDiscoveryBounce()) { mAllAppsController.showDiscoveryBounce(); @@ -1072,7 +1071,7 @@ public class Launcher extends BaseActivity @Override protected void onPause() { // Ensure that items added to Launcher are queued until Launcher returns - InstallShortcutReceiver.enableInstallQueue(); + InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED); super.onPause(); mPaused = true; @@ -1130,18 +1129,6 @@ public class Launcher extends BaseActivity public void setOverlayCallbacks(LauncherOverlayCallbacks callbacks); } - public interface LauncherSearchCallbacks { - /** - * Called when the search overlay is shown. - */ - public void onSearchOverlayOpened(); - - /** - * Called when the search overlay is dismissed. - */ - public void onSearchOverlayClosed(); - } - public interface LauncherOverlayCallbacks { public void onScrollChanged(float progress); } @@ -1297,9 +1284,7 @@ public class Launcher extends BaseActivity private void setupViews() { mDragLayer = (DragLayer) findViewById(R.id.drag_layer); mFocusHandler = mDragLayer.getFocusIndicatorHelper(); - mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace); - mQsbContainer = mDragLayer.findViewById(mDeviceProfile.isVerticalBarLayout() - ? R.id.workspace_blocked_row : R.id.qsb_container); + mWorkspace = mDragLayer.findViewById(R.id.workspace); mWorkspace.initParentViews(mDragLayer); mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN @@ -1334,11 +1319,6 @@ public class Launcher extends BaseActivity // Setup Apps and Widgets mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view); mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view); - if (mLauncherCallbacks != null && mLauncherCallbacks.getAllAppsSearchBarController() != null) { - mAppsView.setSearchBarController(mLauncherCallbacks.getAllAppsSearchBarController()); - } else { - mAppsView.setSearchBarController(new DefaultAppSearchController()); - } // Setup the drag controller (drop targets have to be added in reverse order in priority) mDragController.setMoveTarget(mWorkspace); @@ -1429,8 +1409,8 @@ public class Launcher extends BaseActivity * @return A View inflated from layoutResId. */ public View createShortcut(ViewGroup parent, ShortcutInfo info) { - BubbleTextView favorite = (BubbleTextView) getLayoutInflater().inflate(R.layout.app_icon, - parent, false); + BubbleTextView favorite = (BubbleTextView) LayoutInflater.from(parent.getContext()) + .inflate(R.layout.app_icon, parent, false); favorite.applyFromShortcutInfo(info); favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx); favorite.setOnClickListener(this); @@ -1439,24 +1419,24 @@ public class Launcher extends BaseActivity } /** - * Add a shortcut to the workspace. + * Add a shortcut to the workspace or to a Folder. * * @param data The intent describing the shortcut. */ private void completeAddShortcut(Intent data, long container, long screenId, int cellX, int cellY, PendingRequestArgs args) { - int[] cellXY = mTmpAddItemCellCoordinates; - CellLayout layout = getCellLayout(container, screenId); - - if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT || - args.getPendingIntent().getComponent() == null) { + if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT + || args.getPendingIntent().getComponent() == null) { return; } + int[] cellXY = mTmpAddItemCellCoordinates; + CellLayout layout = getCellLayout(container, screenId); + ShortcutInfo info = null; if (Utilities.isAtLeastO()) { - info = LauncherAppsCompat.createShortcutInfoFromPinItemRequest( - this, PinItemRequestCompat.getPinItemRequest(data), 0); + info = LauncherAppsCompatVO.createShortcutInfoFromPinItemRequest( + this, LauncherAppsCompatVO.getPinItemRequest(data), 0); } if (info == null) { @@ -1475,36 +1455,55 @@ public class Launcher extends BaseActivity } } - final View view = createShortcut(info); - boolean foundCellSpan = false; - // First we check if we already know the exact location where we want to add this item. - if (cellX >= 0 && cellY >= 0) { - cellXY[0] = cellX; - cellXY[1] = cellY; - foundCellSpan = true; + if (container < 0) { + // Adding a shortcut to the Workspace. + final View view = createShortcut(info); + boolean foundCellSpan = false; + // First we check if we already know the exact location where we want to add this item. + if (cellX >= 0 && cellY >= 0) { + cellXY[0] = cellX; + cellXY[1] = cellY; + foundCellSpan = true; - // If appropriate, either create a folder or add to an existing folder - if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0, - true, null,null)) { - return; + // If appropriate, either create a folder or add to an existing folder + if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0, + true, null, null)) { + return; + } + DragObject dragObject = new DragObject(); + dragObject.dragInfo = info; + if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject, + true)) { + return; + } + } else { + foundCellSpan = layout.findCellForSpan(cellXY, 1, 1); } - DragObject dragObject = new DragObject(); - dragObject.dragInfo = info; - if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject, - true)) { + + if (!foundCellSpan) { + mWorkspace.onNoCellFound(layout); return; } + + getModelWriter().addItemToDatabase(info, container, screenId, cellXY[0], cellXY[1]); + mWorkspace.addInScreen(view, info); } else { - foundCellSpan = layout.findCellForSpan(cellXY, 1, 1); - } + // Adding a shortcut to a Folder. + final long folderIconId = container; + FolderIcon folderIcon = (FolderIcon) mWorkspace.getFirstMatch(new ItemOperator() { + @Override + public boolean evaluate(ItemInfo info, View view) { + return info != null && info.id == folderIconId; + } + }); - if (!foundCellSpan) { - mWorkspace.onNoCellFound(layout); - return; + if (folderIcon != null) { + FolderInfo folderInfo = (FolderInfo) folderIcon.getTag(); + folderInfo.add(info, args.rank, false); + } else { + Log.e(TAG, "Could not find folder with id " + folderIconId + " to add shortcut."); + } } - - getModelWriter().addItemToDatabase(info, container, screenId, cellXY[0], cellXY[1]); - mWorkspace.addInScreen(view, info); } /** @@ -1672,10 +1671,6 @@ public class Launcher extends BaseActivity return mWorkspace; } - public View getQsbContainer() { - return mQsbContainer; - } - public Hotseat getHotseat() { return mHotseat; } @@ -1767,7 +1762,7 @@ public class Launcher extends BaseActivity // Reset the apps view if (!alreadyOnHome && mAppsView != null) { - mAppsView.scrollToTop(); + mAppsView.reset(); } // Reset the widgets view @@ -1878,6 +1873,8 @@ public class Launcher extends BaseActivity ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE)) .removeAccessibilityStateChangeListener(this); + WallpaperColorInfo.getInstance(this).setOnThemeChangeListener(null); + LauncherAnimUtils.onDestroyActivity(); if (mLauncherCallbacks != null) { @@ -2469,7 +2466,13 @@ public class Launcher extends BaseActivity private void startAppShortcutOrInfoActivity(View v) { ItemInfo item = (ItemInfo) v.getTag(); - Intent intent = item.getIntent(); + Intent intent; + if (item instanceof PromiseAppInfo) { + PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item; + intent = promiseAppInfo.getMarketIntent(); + } else { + intent = item.getIntent(); + } if (intent == null) { throw new IllegalArgumentException("Input must have a valid intent"); } @@ -2530,8 +2533,8 @@ public class Launcher extends BaseActivity .putExtra(Utilities.EXTRA_WALLPAPER_OFFSET, offset); String pickerPackage = getString(R.string.wallpaper_picker_package); - boolean hasTargetPackage = TextUtils.isEmpty(pickerPackage); - if (!hasTargetPackage) { + boolean hasTargetPackage = !TextUtils.isEmpty(pickerPackage); + if (hasTargetPackage) { intent.setPackage(pickerPackage); } @@ -3397,7 +3400,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); @@ -3600,6 +3603,9 @@ public class Launcher extends BaseActivity LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag(); info.restoreStatus = finalRestoreFlag; + if (info.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) { + info.pendingItemInfo = null; + } mWorkspace.reinflateWidgetsIfNecessary(); getModelWriter().updateItemInDatabase(info); @@ -3678,7 +3684,8 @@ public class Launcher extends BaseActivity mPendingActivityResult = null; } - InstallShortcutReceiver.disableAndFlushInstallQueue(this); + InstallShortcutReceiver.disableAndFlushInstallQueue( + InstallShortcutReceiver.FLAG_LOADER_RUNNING, this); NotificationListener.setNotificationsChangedListener(mPopupDataProvider); @@ -3775,6 +3782,22 @@ public class Launcher extends BaseActivity } @Override + public void bindPromiseAppProgressUpdated(final PromiseAppInfo app) { + Runnable r = new Runnable() { + public void run() { + bindPromiseAppProgressUpdated(app); + } + }; + if (waitUntilResume(r)) { + return; + } + + if (mAppsView != null) { + mAppsView.updatePromiseAppProgress(app); + } + } + + @Override public void bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets) { Runnable r = new Runnable() { public void run() { @@ -3947,7 +3970,7 @@ public class Launcher extends BaseActivity * refreshes the widgets and shortcuts associated with the given package/user */ public void refreshAndBindWidgetsForPackageUser(@Nullable PackageUserKey packageUser) { - mModel.refreshAndBindWidgetsAndShortcuts(this, mWidgetsView.isEmpty(), packageUser); + mModel.refreshAndBindWidgetsAndShortcuts(packageUser); } public void lockScreenOrientation() { @@ -4126,9 +4149,8 @@ public class Launcher extends BaseActivity public void onSharedPreferenceChanged( SharedPreferences sharedPreferences, String key) { if (Utilities.ALLOW_ROTATION_PREFERENCE_KEY.equals(key)) { - // Finish this instance of the activity. When the activity is recreated, - // it will initialize the rotation preference again. - finish(); + // Recreate the activity so that it initializes the rotation preference again. + recreate(); } } } diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java index aa7f5ee5f..cfb9b570b 100644 --- a/src/com/android/launcher3/LauncherAnimUtils.java +++ b/src/com/android/launcher3/LauncherAnimUtils.java @@ -21,11 +21,10 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; +import android.graphics.drawable.Drawable; import android.util.Property; import android.view.View; -import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.widget.ViewAnimator; import java.util.HashSet; import java.util.WeakHashMap; @@ -130,4 +129,16 @@ public class LauncherAnimUtils { return anim; } + public static final Property<Drawable, Integer> DRAWABLE_ALPHA = + new Property<Drawable, Integer>(Integer.TYPE, "drawableAlpha") { + @Override + public Integer get(Drawable drawable) { + return drawable.getAlpha(); + } + + @Override + public void set(Drawable drawable, Integer alpha) { + drawable.setAlpha(alpha); + } + }; } diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 180c202fa..cf20febd5 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.util.ConfigMonitor; import com.android.launcher3.util.Preconditions; @@ -37,7 +37,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; @@ -93,9 +93,7 @@ public class LauncherAppState { mInvariantDeviceProfile = new InvariantDeviceProfile(mContext); mIconCache = new IconCache(mContext, mInvariantDeviceProfile); mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache); - - mModel = new LauncherModel(this, mIconCache, - Utilities.getOverrideObject(AppFilter.class, mContext, R.string.app_filter_class)); + mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext)); LauncherAppsCompat.getInstance(mContext).addOnAppsChangedCallback(mModel); diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java index 13cc7ba07..c7b778252 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java @@ -23,7 +23,6 @@ import android.graphics.PointF; import android.graphics.Rect; import android.os.Handler; import android.os.SystemClock; -import android.util.Log; import android.util.SparseBooleanArray; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -40,9 +39,7 @@ import android.widget.RemoteViews; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragLayer.TouchCompleteListener; -import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.concurrent.Executor; /** * {@inheritDoc} @@ -50,8 +47,6 @@ import java.util.concurrent.Executor; public class LauncherAppWidgetHostView extends AppWidgetHostView implements TouchCompleteListener, View.OnLongClickListener { - private static final String TAG = "LauncherWidgetHostView"; - // Related to the auto-advancing of widgets private static final long ADVANCE_INTERVAL = 20000; private static final long ADVANCE_STAGGER = 250; @@ -98,13 +93,7 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView setBackgroundResource(R.drawable.widget_internal_focus_bg); if (Utilities.isAtLeastO()) { - try { - Method asyncMethod = AppWidgetHostView.class - .getMethod("setExecutor", Executor.class); - asyncMethod.invoke(this, Utilities.THREAD_POOL_EXECUTOR); - } catch (Exception e) { - Log.e(TAG, "Unable to set async executor", e); - } + setExecutor(Utilities.THREAD_POOL_EXECUTOR); } } diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java index 1e0f28546..6f23e56b3 100644 --- a/src/com/android/launcher3/LauncherAppWidgetInfo.java +++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java @@ -21,6 +21,7 @@ import android.content.ComponentName; import android.content.Intent; import android.os.Process; +import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.util.ContentWriter; /** @@ -95,6 +96,11 @@ public class LauncherAppWidgetInfo extends ItemInfo { */ public Intent bindOptions; + /** + * Nonnull for pending widgets. We use this to get the icon and title for the widget. + */ + public PackageItemInfo pendingItemInfo; + private boolean mHasNotifiedInitialWidgetSizeChanged; public LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) { diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java index 2bac11f97..d66b14c7d 100644 --- a/src/com/android/launcher3/LauncherCallbacks.java +++ b/src/com/android/launcher3/LauncherCallbacks.java @@ -17,13 +17,10 @@ package com.android.launcher3; import android.content.Intent; -import android.graphics.Rect; import android.os.Bundle; import android.view.Menu; import android.view.View; -import com.android.launcher3.allapps.AllAppsSearchBarController; -import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.util.ComponentKey; import java.io.FileDescriptor; @@ -44,69 +41,60 @@ public interface LauncherCallbacks { * Activity life-cycle methods. These methods are triggered after * the code in the corresponding Launcher method is executed. */ - public void preOnCreate(); - public void onCreate(Bundle savedInstanceState); - public void preOnResume(); - public void onResume(); - public void onStart(); - public void onStop(); - public void onPause(); - public void onDestroy(); - public void onSaveInstanceState(Bundle outState); - public void onPostCreate(Bundle savedInstanceState); - public void onNewIntent(Intent intent); - public void onActivityResult(int requestCode, int resultCode, Intent data); - public void onRequestPermissionsResult(int requestCode, String[] permissions, + void preOnCreate(); + void onCreate(Bundle savedInstanceState); + void preOnResume(); + void onResume(); + void onStart(); + void onStop(); + void onPause(); + void onDestroy(); + void onSaveInstanceState(Bundle outState); + void onPostCreate(Bundle savedInstanceState); + void onNewIntent(Intent intent); + void onActivityResult(int requestCode, int resultCode, Intent data); + void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults); - public void onWindowFocusChanged(boolean hasFocus); - public void onAttachedToWindow(); - public void onDetachedFromWindow(); - public boolean onPrepareOptionsMenu(Menu menu); - public void dump(String prefix, FileDescriptor fd, PrintWriter w, String[] args); - public void onHomeIntent(); - public boolean handleBackPressed(); - public void onTrimMemory(int level); + void onWindowFocusChanged(boolean hasFocus); + void onAttachedToWindow(); + void onDetachedFromWindow(); + boolean onPrepareOptionsMenu(Menu menu); + void dump(String prefix, FileDescriptor fd, PrintWriter w, String[] args); + void onHomeIntent(); + boolean handleBackPressed(); + void onTrimMemory(int level); /* * Extension points for providing custom behavior on certain user interactions. */ - public void onLauncherProviderChange(); - public void finishBindingItems(final boolean upgradePath); - public void bindAllApplications(ArrayList<AppInfo> apps); - public void onInteractionBegin(); - public void onInteractionEnd(); + void onLauncherProviderChange(); + void finishBindingItems(final boolean upgradePath); + void bindAllApplications(ArrayList<AppInfo> apps); + void onInteractionBegin(); + void onInteractionEnd(); @Deprecated - public void onWorkspaceLockedChanged(); + void onWorkspaceLockedChanged(); /** * Starts a search with {@param initialQuery}. Return false if search was not started. */ - public boolean startSearch( + boolean startSearch( String initialQuery, boolean selectInitialQuery, Bundle appSearchData); - public boolean hasCustomContentToLeft(); - public void populateCustomContentContainer(); - public View getQsbBar(); - public Bundle getAdditionalSearchWidgetOptions(); + boolean hasCustomContentToLeft(); + void populateCustomContentContainer(); + View getQsbBar(); + Bundle getAdditionalSearchWidgetOptions(); /* * Extensions points for adding / replacing some other aspects of the Launcher experience. */ - public boolean shouldMoveToDefaultScreenOnHomeIntent(); - public boolean hasSettings(); - public AllAppsSearchBarController getAllAppsSearchBarController(); - public List<ComponentKey> getPredictedApps(); - public static final int SEARCH_BAR_HEIGHT_NORMAL = 0, SEARCH_BAR_HEIGHT_TALL = 1; + boolean shouldMoveToDefaultScreenOnHomeIntent(); + boolean hasSettings(); + List<ComponentKey> getPredictedApps(); + int SEARCH_BAR_HEIGHT_NORMAL = 0, SEARCH_BAR_HEIGHT_TALL = 1; /** Must return one of {@link #SEARCH_BAR_HEIGHT_NORMAL} or {@link #SEARCH_BAR_HEIGHT_TALL} */ - public int getSearchBarHeight(); + int getSearchBarHeight(); - /** - * Sets the callbacks to allow reacting the actions of search overlays of the launcher. - * - * @param callbacks A set of callbacks to the Launcher, is actually a LauncherSearchCallback, - * but for implementation purposes is passed around as an object. - */ - public void setLauncherSearchCallback(Object callbacks); - - public boolean shouldShowDiscoveryBounce(); + boolean shouldShowDiscoveryBounce(); } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index f881b380a..48c5c562c 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -16,7 +16,6 @@ package com.android.launcher3; -import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProviderOperation; @@ -24,57 +23,40 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.LauncherActivityInfo; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Process; -import android.os.SystemClock; -import android.os.Trace; import android.os.UserHandle; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; -import android.util.LongSparseArray; -import android.util.MutableInt; +import android.util.Pair; -import com.android.launcher3.compat.AppWidgetManagerCompat; 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.dynamicui.ExtractionUtils; -import com.android.launcher3.folder.Folder; -import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.graphics.LauncherIcons; -import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.AddWorkspaceItemsTask; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.CacheDataUpdatedTask; import com.android.launcher3.model.ExtendedModelTask; -import com.android.launcher3.model.GridSizeMigrationTask; -import com.android.launcher3.model.LoaderCursor; +import com.android.launcher3.model.LoaderResults; +import com.android.launcher3.model.LoaderTask; import com.android.launcher3.model.ModelWriter; import com.android.launcher3.model.PackageInstallStateChangedTask; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.model.PackageUpdatedTask; -import com.android.launcher3.model.SdCardAvailableReceiver; import com.android.launcher3.model.ShortcutsChangedTask; import com.android.launcher3.model.UserLockStateChangedTask; import com.android.launcher3.model.WidgetItem; -import com.android.launcher3.model.WidgetsModel; -import com.android.launcher3.provider.ImportDataTask; import com.android.launcher3.provider.LauncherDbUtils; 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.ManagedProfileHeuristic; import com.android.launcher3.util.MultiHashMap; -import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Provider; @@ -85,14 +67,9 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; import java.util.HashSet; 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; @@ -103,20 +80,17 @@ import java.util.concurrent.Executor; */ public class LauncherModel extends BroadcastReceiver implements LauncherAppsCompat.OnAppsChangedCallbackCompat { - static final boolean DEBUG_LOADERS = false; + static final boolean DEBUG_TASKS = false; private static final boolean DEBUG_RECEIVER = false; static final String TAG = "Launcher.Model"; - 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 + LoaderTask mLoaderTask; @Thunk boolean mIsLoaderTaskRunning; - @Thunk boolean mHasLoaderCompletedOnce; @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); static { @@ -135,20 +109,17 @@ public class LauncherModel extends BroadcastReceiver } } - /** - * Set of runnables to be called on the background thread after the workspace binding - * is complete. - */ - static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>(); - @Thunk WeakReference<Callbacks> mCallbacks; // < only access in worker thread > private final AllAppsList mBgAllAppsList; - // Entire list of widgets. - private final WidgetsModel mBgWidgetsModel; - private boolean mHasShortcutHostPermission; + /** + * All the static data should be accessed on the background thread, A lock should be acquired + * on this object when accessing any data from this model. + */ + static final BgDataModel sBgDataModel = new BgDataModel(); + // Runnable to check if the shortcuts permission has changed. private final Runnable mShortcutPermissionCheckRunnable = new Runnable() { @Override @@ -156,26 +127,13 @@ public class LauncherModel extends BroadcastReceiver if (mModelLoaded) { boolean hasShortcutHostPermission = DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission(); - if (hasShortcutHostPermission != mHasShortcutHostPermission) { + if (hasShortcutHostPermission != sBgDataModel.hasShortcutHostPermission) { forceReload(); } } } }; - /** - * All the static data should be accessed on the background thread, A lock should be acquired - * on this object when accessing any data from this model. - */ - static final BgDataModel sBgDataModel = new BgDataModel(); - - // </ only access in worker thread > - - private final IconCache mIconCache; - - private final LauncherAppsCompat mLauncherApps; - private final UserManagerCompat mUserManager; - public interface Callbacks { public boolean setLoadOnResume(); public int getCurrentWorkspaceScreen(); @@ -193,6 +151,7 @@ public class LauncherModel extends BroadcastReceiver ArrayList<ItemInfo> addAnimated, ArrayList<AppInfo> addedApps); public void bindAppsUpdated(ArrayList<AppInfo> apps); + public void bindPromiseAppProgressUpdated(PromiseAppInfo app); public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, ArrayList<ShortcutInfo> removed, UserHandle user); public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); @@ -209,25 +168,8 @@ public class LauncherModel extends BroadcastReceiver } LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) { - Context context = app.getContext(); mApp = app; mBgAllAppsList = new AllAppsList(iconCache, appFilter); - mBgWidgetsModel = new WidgetsModel(iconCache, appFilter); - mIconCache = iconCache; - - mLauncherApps = LauncherAppsCompat.getInstance(context); - 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 @@ -258,15 +200,8 @@ public class LauncherModel extends BroadcastReceiver /** * Adds the provided items to the workspace. */ - public void addAndBindAddedWorkspaceItems(List<ItemInfo> workspaceApps) { - addAndBindAddedWorkspaceItems(Provider.of(workspaceApps)); - } - - /** - * Adds the provided items to the workspace. - */ public void addAndBindAddedWorkspaceItems( - Provider<List<ItemInfo>> appsProvider) { + Provider<List<Pair<ItemInfo, Object>>> appsProvider) { enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider)); } @@ -379,8 +314,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); } } @@ -538,27 +471,36 @@ public class LauncherModel extends BroadcastReceiver */ public boolean startLoader(int synchronousBindPage) { // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems - InstallShortcutReceiver.enableInstallQueue(); + InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING); synchronized (mLock) { // Don't bother to start the thread if we know it's not going to do anything 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); + LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel, + mBgAllAppsList, synchronousBindPage, mCallbacks); if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mModelLoaded && !mIsLoaderTaskRunning) { - mLoaderTask.runBindSynchronousPage(synchronousBindPage); + + // Divide the set of loaded items into those that we are binding synchronously, + // and everything else that is to be bound normally (asynchronously). + loaderResults.bindWorkspace(); + // For now, continue posting the binding of AllApps as there are other + // issues that arise from that. + loaderResults.bindAllApps(); + loaderResults.bindDeepShortcuts(); + loaderResults.bindWidgets(); return true; } else { - sWorkerThread.setPriority(Thread.NORM_PRIORITY); + mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, loaderResults); sWorker.post(mLoaderTask); } } @@ -586,1211 +528,61 @@ public class LauncherModel extends BroadcastReceiver screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK)); } - /** - * Runnable for the thread that loads the contents of the launcher: - * - workspace icons - * - widgets - * - all apps icons - * - deep shortcuts within apps - */ - private class LoaderTask implements Runnable { - private Context mContext; - private int mPageToBindFirst; - - @Thunk boolean mIsLoadingAndBindingWorkspace; - private boolean mStopped; - @Thunk boolean mLoadAndBindStepFinished; - - LoaderTask(Context context, int pageToBindFirst) { - mContext = context; - mPageToBindFirst = pageToBindFirst; - } - - 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(); - } + public void onInstallSessionCreated(final PackageInstallInfo sessionInfo) { + enqueueModelUpdateTask(new ExtendedModelTask() { + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + apps.addPromiseApp(app.getContext(), sessionInfo); + if (!apps.added.isEmpty()) { + final ArrayList<AppInfo> arrayList = new ArrayList<>(apps.added); + apps.added.clear(); + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindAppsAdded(null, null, null, arrayList); } }); - - 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"); - } - } - } - - void runBindSynchronousPage(int synchronousBindPage) { - if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) { - // Ensure that we have a valid page index to load synchronously - throw new RuntimeException("Should not call runBindSynchronousPage() without " + - "valid page index"); - } - 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"); } - synchronized (mLock) { - if (mIsLoaderTaskRunning) { - // Ensure that we are never running the background loading at this point since - // we also touch the background collections - throw new RuntimeException("Error! Background loading is already running"); - } - } - - // 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(); + public class LoaderTransaction implements AutoCloseable { - // 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); - // XXX: For now, continue posting the binding of AllApps as there are other issues that - // arise from that. - onlyBindAllApps(); + private final LoaderTask mTask; - bindDeepShortcuts(); - } - - private void verifyNotStopped() throws CancellationException { - synchronized (LoaderTask.this) { - if (mStopped) { - throw new CancellationException("Loader stopped"); - } - } - } - - public void run() { + private LoaderTransaction(LoaderTask task) throws CancellationException { synchronized (mLock) { - if (mStopped) { - return; + if (mLoaderTask != task) { + throw new CancellationException("Loader already stopped"); } + mTask = task; mIsLoaderTaskRunning = true; - } - - try { - 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"); - waitForIdle(); - verifyNotStopped(); - - // second step - 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"); - waitForIdle(); - verifyNotStopped(); - - // third step - if (DEBUG_LOADERS) Log.d(TAG, "step 3.1: loading deep shortcuts"); - loadDeepShortcuts(); - - verifyNotStopped(); - if (DEBUG_LOADERS) Log.d(TAG, "step 3.2: bind deep shortcuts"); - bindDeepShortcuts(); - - // Take a break - if (DEBUG_LOADERS) Log.d(TAG, "step 3 completed, wait for idle"); - waitForIdle(); - verifyNotStopped(); - - // fourth step - if (DEBUG_LOADERS) Log.d(TAG, "step 4.1: loading widgets"); - refreshAndBindWidgetsAndShortcuts(getCallback(), false /* bindFirst */, - null /* packageUser */); - - 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; - } - } - } - - public void stopLocked() { - synchronized (LoaderTask.this) { - mStopped = true; - this.notify(); + mModelLoaded = false; } } - /** - * Gets the callbacks object. If we've been stopped, or if the launcher object - * has somehow been garbage collected, return null instead. Pass in the Callbacks - * object that was around when the deferred message was scheduled, and if there's - * a new Callbacks object around then also return null. This will save us from - * calling onto it with data that will be ignored. - */ - Callbacks tryGetCallbacks(Callbacks oldCallbacks) { + public void commit() { synchronized (mLock) { - if (mStopped) { - return null; - } - - if (mCallbacks == null) { - return null; - } - - final Callbacks callbacks = mCallbacks.get(); - if (callbacks != oldCallbacks) { - return null; - } - if (callbacks == null) { - Log.w(TAG, "no mCallbacks"); - return null; - } - - return callbacks; - } - } - - private void loadWorkspace() { - if (LauncherAppState.PROFILE_STARTUP) { - Trace.beginSection("Loading Workspace"); - } - - final Context context = mContext; - final ContentResolver contentResolver = context.getContentResolver(); - final PackageManagerHelper pmHelper = new PackageManagerHelper(context); - final boolean isSafeMode = pmHelper.isSafeMode(); - final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); - final DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(context); - final boolean isSdCardReady = Utilities.isBootCompleted(); - final MultiHashMap<UserHandle, String> pendingPackages = new MultiHashMap<>(); - - boolean clearDb = false; - try { - ImportDataTask.performImportIfPossible(context); - } catch (Exception e) { - // Migration failed. Clear workspace. - clearDb = true; - } - - if (!clearDb && GridSizeMigrationTask.ENABLED && - !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) { - // Migration failed. Clear workspace. - clearDb = true; - } - - if (clearDb) { - Log.d(TAG, "loadWorkspace: resetting launcher database"); - LauncherSettings.Settings.call(contentResolver, - LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); - } - - Log.d(TAG, "loadWorkspace: loading default favorites"); - LauncherSettings.Settings.call(contentResolver, - LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES); - - synchronized (sBgDataModel) { - sBgDataModel.clear(); - - final HashMap<String, Integer> installingPkgs = PackageInstallerCompat - .getInstance(mContext).updateAndGetActiveSessionCache(); - sBgDataModel.workspaceScreens.addAll(loadWorkspaceScreensDb(mContext)); - - Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>(); - final LoaderCursor c = new LoaderCursor(contentResolver.query( - LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp); - - HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null; - - try { - final int appWidgetIdIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.APPWIDGET_ID); - final int appWidgetProviderIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.APPWIDGET_PROVIDER); - final int spanXIndex = c.getColumnIndexOrThrow - (LauncherSettings.Favorites.SPANX); - final int spanYIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.SPANY); - final int rankIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.RANK); - final int optionsIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.OPTIONS); - - final LongSparseArray<UserHandle> allUsers = c.allUsers; - final LongSparseArray<Boolean> quietMode = new LongSparseArray<>(); - final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>(); - for (UserHandle user : mUserManager.getUserProfiles()) { - long serialNo = mUserManager.getSerialNumberForUser(user); - allUsers.put(serialNo, user); - quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user)); - - boolean userUnlocked = mUserManager.isUserUnlocked(user); - - // We can only query for shortcuts when the user is unlocked. - if (userUnlocked) { - List<ShortcutInfoCompat> pinnedShortcuts = - shortcutManager.queryForPinnedShortcuts(null, user); - if (shortcutManager.wasLastCallSuccess()) { - for (ShortcutInfoCompat shortcut : pinnedShortcuts) { - shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), - shortcut); - } - } else { - // Shortcut manager can fail due to some race condition when the - // lock state changes too frequently. For the purpose of the loading - // shortcuts, consider the user is still locked. - userUnlocked = false; - } - } - unlockedUsers.put(serialNo, userUnlocked); - } - - ShortcutInfo info; - LauncherAppWidgetInfo appWidgetInfo; - Intent intent; - String targetPkg; - - while (!mStopped && c.moveToNext()) { - try { - if (c.user == null) { - // User has been deleted, remove the item. - c.markDeleted("User has been deleted"); - continue; - } - - boolean allowMissingTarget = false; - switch (c.itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: - intent = c.parseIntent(); - if (intent == null) { - c.markDeleted("Invalid or null intent"); - continue; - } - - int disabledState = quietMode.get(c.serialNumber) ? - ShortcutInfo.FLAG_DISABLED_QUIET_USER : 0; - ComponentName cn = intent.getComponent(); - targetPkg = cn == null ? intent.getPackage() : cn.getPackageName(); - - if (!Process.myUserHandle().equals(c.user)) { - if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { - c.markDeleted("Legacy shortcuts are only allowed for default user"); - continue; - } else if (c.restoreFlag != 0) { - // Don't restore items for other profiles. - c.markDeleted("Restore from managed profile not supported"); - continue; - } - } - if (TextUtils.isEmpty(targetPkg) && - c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { - c.markDeleted("Only legacy shortcuts can have null package"); - continue; - } - - // If there is no target package, its an implicit intent - // (legacy shortcut) which is always valid - boolean validTarget = TextUtils.isEmpty(targetPkg) || - launcherApps.isPackageEnabledForProfile(targetPkg, c.user); - - if (cn != null && validTarget) { - // If the apk is present and the shortcut points to a specific - // component. - - // If the component is already present - if (launcherApps.isActivityEnabledForProfile(cn, c.user)) { - // no special handling necessary for this item - c.markRestored(); - } else { - if (c.hasRestoreFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) { - // We allow auto install apps to have their intent - // updated after an install. - intent = pmHelper.getAppLaunchIntent(targetPkg, c.user); - if (intent != null) { - c.restoreFlag = 0; - c.updater().put( - LauncherSettings.Favorites.INTENT, - intent.toUri(0)).commit(); - cn = intent.getComponent(); - } else { - c.markDeleted("Unable to find a launch target"); - continue; - } - } else { - // The app is installed but the component is no - // longer available. - c.markDeleted("Invalid component removed: " + cn); - continue; - } - } - } - // else if cn == null => can't infer much, leave it - // else if !validPkg => could be restored icon or missing sd-card - - if (!TextUtils.isEmpty(targetPkg) && !validTarget) { - // Points to a valid app (superset of cn != null) but the apk - // is not available. - - if (c.restoreFlag != 0) { - // Package is not yet available but might be - // installed later. - FileLog.d(TAG, "package not yet restored: " + targetPkg); - - if (c.hasRestoreFlag(ShortcutInfo.FLAG_RESTORE_STARTED)) { - // Restore has started once. - } else if (installingPkgs.containsKey(targetPkg)) { - // App restore has started. Update the flag - c.restoreFlag |= ShortcutInfo.FLAG_RESTORE_STARTED; - c.updater().commit(); - } else { - c.markDeleted("Unrestored app removed: " + targetPkg); - continue; - } - } 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. - allowMissingTarget = true; - } else if (!isSdCardReady) { - // SdCard is not ready yet. Package might get available, - // once it is ready. - Log.d(TAG, "Missing pkg, will check later: " + targetPkg); - pendingPackages.addToList(c.user, targetPkg); - // Add the icon on the workspace anyway. - allowMissingTarget = true; - } else { - // Do not wait for external media load anymore. - c.markDeleted("Invalid package removed: " + targetPkg); - continue; - } - } - - if (validTarget) { - // The shortcut points to a valid target (either no target - // or something which is ready to be used) - c.markRestored(); - } - - boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() && - c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW; - - if (c.restoreFlag != 0) { - // Already verified above that user is same as default user - info = c.getRestoredItemInfo(intent); - } else if (c.itemType == - LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - info = c.getAppShortcutInfo( - intent, allowMissingTarget, useLowResIcon); - } else if (c.itemType == - LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { - - ShortcutKey key = ShortcutKey.fromIntent(intent, c.user); - if (unlockedUsers.get(c.serialNumber)) { - ShortcutInfoCompat pinnedShortcut = - shortcutKeyToPinnedShortcuts.get(key); - if (pinnedShortcut == null) { - // The shortcut is no longer valid. - c.markDeleted("Pinned shortcut not found"); - continue; - } - 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. - info = c.loadSimpleShortcut(); - info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER; - } - } else { // item type == ITEM_TYPE_SHORTCUT - info = c.loadSimpleShortcut(); - - // Shortcuts are only available on the primary profile - if (!TextUtils.isEmpty(targetPkg) - && pmHelper.isAppSuspended(targetPkg, c.user)) { - disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; - } - - // App shortcuts that used to be automatically added to Launcher - // didn't always have the correct intent flags set, so do that - // here - if (intent.getAction() != null && - intent.getCategories() != null && - intent.getAction().equals(Intent.ACTION_MAIN) && - intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { - intent.addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - } - } - - if (info != null) { - c.applyCommonProperties(info); - - info.intent = intent; - info.rank = c.getInt(rankIndex); - info.spanX = 1; - info.spanY = 1; - info.isDisabled |= disabledState; - if (isSafeMode && !Utilities.isSystemApp(context, intent)) { - info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE; - } - - if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) { - Integer progress = installingPkgs.get(targetPkg); - if (progress != null) { - info.setInstallProgress(progress); - } else { - info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; - } - } - - c.checkAndAddItem(info, sBgDataModel); - } else { - throw new RuntimeException("Unexpected null ShortcutInfo"); - } - break; - - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(c.id); - c.applyCommonProperties(folderInfo); - - // Do not trim the folder label, as is was set by the user. - folderInfo.title = c.getString(c.titleIndex); - folderInfo.spanX = 1; - folderInfo.spanY = 1; - folderInfo.options = c.getInt(optionsIndex); - - // no special handling required for restored folders - c.markRestored(); - - c.checkAndAddItem(folderInfo, sBgDataModel); - break; - - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: - // Read all Launcher-specific widget details - boolean customWidget = c.itemType == - LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; - - int appWidgetId = c.getInt(appWidgetIdIndex); - String savedProvider = c.getString(appWidgetProviderIndex); - - final ComponentName component = - ComponentName.unflattenFromString(savedProvider); - - final boolean isIdValid = !c.hasRestoreFlag( - LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); - final boolean wasProviderReady = !c.hasRestoreFlag( - LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY); - - if (widgetProvidersMap == null) { - widgetProvidersMap = AppWidgetManagerCompat - .getInstance(mContext).getAllProvidersMap(); - } - final AppWidgetProviderInfo provider = widgetProvidersMap.get( - new ComponentKey( - ComponentName.unflattenFromString(savedProvider), - c.user)); - - final boolean isProviderReady = isValidProvider(provider); - if (!isSafeMode && !customWidget && - wasProviderReady && !isProviderReady) { - c.markDeleted( - "Deleting widget that isn't installed anymore: " - + provider); - } else { - if (isProviderReady) { - appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, - provider.provider); - - // The provider is available. So the widget is either - // available or not available. We do not need to track - // any future restore updates. - int status = c.restoreFlag & - ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; - if (!wasProviderReady) { - // If provider was not previously ready, update the - // status and UI flag. - - // Id would be valid only if the widget restore broadcast was received. - if (isIdValid) { - status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY; - } else { - status &= ~LauncherAppWidgetInfo - .FLAG_PROVIDER_NOT_READY; - } - } - appWidgetInfo.restoreStatus = status; - } else { - Log.v(TAG, "Widget restore pending id=" + c.id - + " appWidgetId=" + appWidgetId - + " status =" + c.restoreFlag); - appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, - component); - appWidgetInfo.restoreStatus = c.restoreFlag; - Integer installProgress = installingPkgs.get(component.getPackageName()); - - if (c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED)) { - // Restore has started once. - } else if (installProgress != null) { - // App restore has started. Update the flag - appWidgetInfo.restoreStatus |= - LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; - } else if (!isSafeMode) { - c.markDeleted("Unrestored widget removed: " + component); - continue; - } - - appWidgetInfo.installProgress = - installProgress == null ? 0 : installProgress; - } - if (appWidgetInfo.hasRestoreFlag( - LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) { - appWidgetInfo.bindOptions = c.parseIntent(); - } - - c.applyCommonProperties(appWidgetInfo); - appWidgetInfo.spanX = c.getInt(spanXIndex); - appWidgetInfo.spanY = c.getInt(spanYIndex); - appWidgetInfo.user = c.user; - - if (!c.isOnWorkspaceOrHotseat()) { - c.markDeleted("Widget found where container != " + - "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); - continue; - } - - if (!customWidget) { - String providerName = - appWidgetInfo.providerName.flattenToString(); - if (!providerName.equals(savedProvider) || - (appWidgetInfo.restoreStatus != c.restoreFlag)) { - c.updater() - .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, - providerName) - .put(LauncherSettings.Favorites.RESTORED, - appWidgetInfo.restoreStatus) - .commit(); - } - } - c.checkAndAddItem(appWidgetInfo, sBgDataModel); - } - break; - } - } catch (Exception e) { - Log.e(TAG, "Desktop items loading interrupted", e); - } - } - } finally { - Utilities.closeSilently(c); - } - - // Break early if we've stopped loading - if (mStopped) { - sBgDataModel.clear(); - return; - } - - // Remove dead items - if (c.commitDeleted()) { - // Remove any empty folder - ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings - .call(contentResolver, - LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS) - .getSerializable(LauncherSettings.Settings.EXTRA_VALUE); - for (long folderId : deletedFolderIds) { - sBgDataModel.workspaceItems.remove(sBgDataModel.folders.get(folderId)); - sBgDataModel.folders.remove(folderId); - sBgDataModel.itemsIdMap.remove(folderId); - } - - // Remove any ghost widgets - LauncherSettings.Settings.call(contentResolver, - LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS); - } - - // Unpin shortcuts that don't exist on the workspace. - HashSet<ShortcutKey> pendingShortcuts = - InstallShortcutReceiver.getPendingShortcuts(context); - for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) { - MutableInt numTimesPinned = sBgDataModel.pinnedShortcutCounts.get(key); - if ((numTimesPinned == null || numTimesPinned.value == 0) - && !pendingShortcuts.contains(key)) { - // Shortcut is pinned but doesn't exist on the workspace; unpin it. - shortcutManager.unpinShortcut(key); - } - } - - // Sort all the folder items and make sure the first 3 items are high resolution. - for (FolderInfo folder : sBgDataModel.folders) { - Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR); - int pos = 0; - for (ShortcutInfo info : folder.contents) { - if (info.usingLowResIcon && - info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - mIconCache.getTitleAndIcon(info, false); - } - pos ++; - if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) { - break; - } - } - } - - c.commitRestoredItems(); - if (!isSdCardReady && !pendingPackages.isEmpty()) { - context.registerReceiver( - new SdCardAvailableReceiver( - LauncherModel.this, mContext, pendingPackages), - new IntentFilter(Intent.ACTION_BOOT_COMPLETED), - null, - sWorker); - } - - // Remove any empty screens - ArrayList<Long> unusedScreens = new ArrayList<>(sBgDataModel.workspaceScreens); - for (ItemInfo item: sBgDataModel.itemsIdMap) { - long screenId = item.screenId; - if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && - unusedScreens.contains(screenId)) { - unusedScreens.remove(screenId); - } - } - - // If there are any empty screens remove them, and update. - if (unusedScreens.size() != 0) { - sBgDataModel.workspaceScreens.removeAll(unusedScreens); - updateWorkspaceScreenOrder(context, sBgDataModel.workspaceScreens); - } - } - if (LauncherAppState.PROFILE_STARTUP) { - Trace.endSection(); - } - } - - /** Filters the set of items who are directly or indirectly (via another container) on the - * specified screen. */ - private void filterCurrentWorkspaceItems(long currentScreenId, - ArrayList<ItemInfo> allWorkspaceItems, - ArrayList<ItemInfo> currentScreenItems, - ArrayList<ItemInfo> otherScreenItems) { - // Purge any null ItemInfos - Iterator<ItemInfo> iter = allWorkspaceItems.iterator(); - while (iter.hasNext()) { - ItemInfo i = iter.next(); - if (i == null) { - iter.remove(); - } - } - - // Order the set of items by their containers first, this allows use to walk through the - // list sequentially, build up a list of containers that are in the specified screen, - // as well as all items in those containers. - Set<Long> itemsOnScreen = new HashSet<Long>(); - Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() { - @Override - public int compare(ItemInfo lhs, ItemInfo rhs) { - return Utilities.longCompare(lhs.container, rhs.container); - } - }); - for (ItemInfo info : allWorkspaceItems) { - if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { - if (info.screenId == currentScreenId) { - currentScreenItems.add(info); - itemsOnScreen.add(info.id); - } else { - otherScreenItems.add(info); - } - } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - currentScreenItems.add(info); - itemsOnScreen.add(info.id); - } else { - if (itemsOnScreen.contains(info.container)) { - currentScreenItems.add(info); - itemsOnScreen.add(info.id); - } else { - otherScreenItems.add(info); - } - } - } - } - - /** Filters the set of widgets which are on the specified screen. */ - private void filterCurrentAppWidgets(long currentScreenId, - ArrayList<LauncherAppWidgetInfo> appWidgets, - ArrayList<LauncherAppWidgetInfo> currentScreenWidgets, - ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) { - - for (LauncherAppWidgetInfo widget : appWidgets) { - if (widget == null) continue; - if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && - widget.screenId == currentScreenId) { - currentScreenWidgets.add(widget); - } else { - otherScreenWidgets.add(widget); - } - } - } - - /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to - * right) */ - private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) { - final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile(); - final int screenCols = profile.numColumns; - final int screenCellCount = profile.numColumns * profile.numRows; - Collections.sort(workspaceItems, new Comparator<ItemInfo>() { - @Override - public int compare(ItemInfo lhs, ItemInfo rhs) { - if (lhs.container == rhs.container) { - // Within containers, order by their spatial position in that container - switch ((int) lhs.container) { - case LauncherSettings.Favorites.CONTAINER_DESKTOP: { - long lr = (lhs.screenId * screenCellCount + - lhs.cellY * screenCols + lhs.cellX); - long rr = (rhs.screenId * screenCellCount + - rhs.cellY * screenCols + rhs.cellX); - return Utilities.longCompare(lr, rr); - } - case LauncherSettings.Favorites.CONTAINER_HOTSEAT: { - // We currently use the screen id as the rank - return Utilities.longCompare(lhs.screenId, rhs.screenId); - } - default: - if (ProviderConfig.IS_DOGFOOD_BUILD) { - throw new RuntimeException("Unexpected container type when " + - "sorting workspace items."); - } - return 0; - } - } else { - // Between containers, order by hotseat, desktop - return Utilities.longCompare(lhs.container, rhs.container); - } - } - }); - } - - private void bindWorkspaceScreens(final Callbacks oldCallbacks, - final ArrayList<Long> orderedScreens) { - final Runnable r = new Runnable() { - @Override - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.bindScreens(orderedScreens); - } - } - }; - runOnMainThread(r); - } - - private void bindWorkspaceItems(final Callbacks oldCallbacks, - final ArrayList<ItemInfo> workspaceItems, - final ArrayList<LauncherAppWidgetInfo> appWidgets, - final Executor executor) { - - // Bind the workspace items - int N = workspaceItems.size(); - for (int i = 0; i < N; i += ITEMS_CHUNK) { - final int start = i; - final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); - final Runnable r = new Runnable() { - @Override - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.bindItems(workspaceItems, start, start+chunkSize, - false); - } - } - }; - executor.execute(r); - } - - // Bind the widgets, one at a time - N = appWidgets.size(); - for (int i = 0; i < N; i++) { - final LauncherAppWidgetInfo widget = appWidgets.get(i); - final Runnable r = new Runnable() { - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.bindAppWidget(widget); - } - } - }; - executor.execute(r); - } - } - - /** - * Binds all loaded data to actual views on the main thread. - */ - private void bindWorkspace(int synchronizeBindPage) { - final long t = SystemClock.uptimeMillis(); - Runnable r; - - // Don't use these two variables in any of the callback runnables. - // Otherwise we hold a reference to them. - final Callbacks oldCallbacks = mCallbacks.get(); - if (oldCallbacks == null) { - // This launcher has exited and nobody bothered to tell us. Just bail. - Log.w(TAG, "LoaderTask running with no launcher"); - return; - } - - // Save a copy of all the bg-thread collections - ArrayList<ItemInfo> workspaceItems = new ArrayList<>(); - ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); - ArrayList<Long> orderedScreenIds = new ArrayList<>(); - - synchronized (sBgDataModel) { - workspaceItems.addAll(sBgDataModel.workspaceItems); - appWidgets.addAll(sBgDataModel.appWidgets); - orderedScreenIds.addAll(sBgDataModel.workspaceScreens); - } - - final int currentScreen; - { - int currScreen = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE - ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen(); - if (currScreen >= orderedScreenIds.size()) { - // There may be no workspace screens (just hotseat items and an empty page). - currScreen = PagedView.INVALID_RESTORE_PAGE; - } - currentScreen = currScreen; - } - final boolean validFirstPage = currentScreen >= 0; - final long currentScreenId = - validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID; - - // Separate the items that are on the current screen, and all the other remaining items - ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>(); - ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>(); - ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>(); - ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>(); - - filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, - otherWorkspaceItems); - filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets, - otherAppWidgets); - sortWorkspaceItemsSpatially(currentWorkspaceItems); - sortWorkspaceItemsSpatially(otherWorkspaceItems); - - // Tell the workspace that we're about to start binding items - r = new Runnable() { - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.clearPendingBinds(); - callbacks.startBinding(); - } - } - }; - runOnMainThread(r); - - bindWorkspaceScreens(oldCallbacks, orderedScreenIds); - - Executor mainExecutor = new DeferredMainThreadExecutor(); - // Load items on the current page. - bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor); - - // In case of validFirstPage, only bind the first screen, and defer binding the - // remaining screens after first onDraw (and an optional the fade animation whichever - // happens later). - // 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; - - mainExecutor.execute(new Runnable() { - @Override - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.finishFirstPageBind( - validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null); - } - } - }); - - bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor); - - // Tell the workspace that we're done binding items - r = new Runnable() { - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.finishBindingItems(); - } - - mIsLoadingAndBindingWorkspace = false; - - // Run all the bind complete runnables after workspace is bound. - if (!mBindCompleteRunnables.isEmpty()) { - synchronized (mBindCompleteRunnables) { - for (final Runnable r : mBindCompleteRunnables) { - runOnWorkerThread(r); - } - mBindCompleteRunnables.clear(); - } - } - - // If we're profiling, ensure this is the last thing in the queue. - if (DEBUG_LOADERS) { - Log.d(TAG, "bound workspace in " - + (SystemClock.uptimeMillis()-t) + "ms"); - } - - } - }; - deferredExecutor.execute(r); - - if (validFirstPage) { - r = new Runnable() { - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - // We are loading synchronously, which means, some of the pages will be - // bound after first draw. Inform the callbacks that page binding is - // not complete, and schedule the remaining pages. - if (currentScreen != PagedView.INVALID_RESTORE_PAGE) { - callbacks.onPageBoundSynchronously(currentScreen); - } - callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor); - } - } - }; - runOnMainThread(r); + // Everything loaded bind the data. + mModelLoaded = true; } } - private void updateIconCache() { - // Ignore packages which have a promise icon. - HashSet<String> packagesToIgnore = new HashSet<>(); - synchronized (sBgDataModel) { - for (ItemInfo info : sBgDataModel.itemsIdMap) { - if (info instanceof ShortcutInfo) { - ShortcutInfo si = (ShortcutInfo) info; - if (si.isPromise() && si.getTargetComponent() != null) { - packagesToIgnore.add(si.getTargetComponent().getPackageName()); - } - } else if (info instanceof LauncherAppWidgetInfo) { - LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info; - if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) { - packagesToIgnore.add(lawi.providerName.getPackageName()); - } - } - } - } - mIconCache.updateDbIcons(packagesToIgnore); - } - - private void onlyBindAllApps() { - final Callbacks oldCallbacks = mCallbacks.get(); - if (oldCallbacks == null) { - // This launcher has exited and nobody bothered to tell us. Just bail. - Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)"); - return; - } - - // shallow copy - @SuppressWarnings("unchecked") - final ArrayList<AppInfo> list - = (ArrayList<AppInfo>) mBgAllAppsList.data.clone(); - Runnable r = new Runnable() { - public void run() { - final long t = SystemClock.uptimeMillis(); - final Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.bindAllApplications(list); - } - if (DEBUG_LOADERS) { - Log.d(TAG, "bound all " + list.size() + " apps from cache in " - + (SystemClock.uptimeMillis() - t) + "ms"); - } - } - }; - runOnMainThread(r); - } - - private void loadAllApps() { - final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; - - final Callbacks oldCallbacks = mCallbacks.get(); - if (oldCallbacks == null) { - // This launcher has exited and nobody bothered to tell us. Just bail. - Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)"); - return; - } - - final List<UserHandle> profiles = mUserManager.getUserProfiles(); - - // Clear the list of apps - mBgAllAppsList.clear(); - for (UserHandle user : profiles) { - // Query for the set of apps - final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; - final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user); - if (DEBUG_LOADERS) { - Log.d(TAG, "getActivityList took " - + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user); - Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user); - } - // Fail if we don't have any apps - // TODO: Fix this. Only fail for the current user. - if (apps == null || apps.isEmpty()) { - return; - } - boolean quietMode = mUserManager.isQuietModeEnabled(user); - // Create the ApplicationInfos - for (int i = 0; i < apps.size(); i++) { - LauncherActivityInfo app = apps.get(i); - // This builds the icon bitmaps. - mBgAllAppsList.add(new AppInfo(app, user, quietMode), app); - } - - final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user); - if (heuristic != null) { - final Runnable r = new Runnable() { - - @Override - public void run() { - 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); - } - } - }); - } - } - // Huh? Shouldn't this be inside the Runnable below? - final ArrayList<AppInfo> added = mBgAllAppsList.added; - mBgAllAppsList.added = new ArrayList<AppInfo>(); - - // Post callback on main thread - mHandler.post(new Runnable() { - public void run() { - - final long bindTime = SystemClock.uptimeMillis(); - final Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.bindAllApplications(added); - if (DEBUG_LOADERS) { - Log.d(TAG, "bound " + added.size() + " apps in " - + (SystemClock.uptimeMillis() - bindTime) + "ms"); - } - } else { - Log.i(TAG, "not binding apps: no Launcher activity"); - } - } - }); - // Cleanup any data stored for a deleted user. - ManagedProfileHeuristic.processAllUsers(profiles, mContext); - if (DEBUG_LOADERS) { - Log.d(TAG, "Icons processed in " - + (SystemClock.uptimeMillis() - loadTime) + "ms"); - } - } - - private void loadDeepShortcuts() { - sBgDataModel.deepShortcutMap.clear(); - DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext); - mHasShortcutHostPermission = shortcutManager.hasHostPermission(); - if (mHasShortcutHostPermission) { - for (UserHandle user : mUserManager.getUserProfiles()) { - if (mUserManager.isUserUnlocked(user)) { - List<ShortcutInfoCompat> shortcuts = - shortcutManager.queryForAllShortcuts(user); - sBgDataModel.updateDeepShortcutMap(null, user, shortcuts); - } + @Override + public void close() { + synchronized (mLock) { + // If we are still the last one to be scheduled, remove ourselves. + if (mLoaderTask == mTask) { + mLoaderTask = null; } + mIsLoaderTaskRunning = false; } } } - public void bindDeepShortcuts() { - final MultiHashMap<ComponentKey, String> shortcutMapCopy = - sBgDataModel.deepShortcutMap.clone(); - Runnable r = new Runnable() { - @Override - public void run() { - Callbacks callbacks = getCallback(); - if (callbacks != null) { - callbacks.bindDeepShortcutMap(shortcutMapCopy); - } - } - }; - runOnMainThread(r); + public LoaderTransaction beginLoader(LoaderTask task) throws CancellationException { + return new LoaderTransaction(task); } /** @@ -1815,13 +607,7 @@ public class LauncherModel extends BroadcastReceiver CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages)); } - void enqueueModelUpdateTask(BaseModelUpdateTask task) { - if (!mModelLoaded && mLoaderTask == null) { - if (DEBUG_LOADERS) { - Log.d(TAG, "enqueueModelUpdateTask Ignoring task since loader is pending=" + task); - } - return; - } + public void enqueueModelUpdateTask(BaseModelUpdateTask task) { task.init(this); runOnWorkerThread(task); } @@ -1841,17 +627,20 @@ 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 - public void run() { - if (!mModel.mHasLoaderCompletedOnce) { + public final void run() { + if (!mModel.mModelLoaded) { + if (DEBUG_TASKS) { + Log.d(TAG, "Ignoring model task since loader is pending=" + this); + } // Loader has not yet run. return; } @@ -1869,7 +658,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) { @@ -1911,43 +700,16 @@ public class LauncherModel extends BroadcastReceiver }); } - private void bindWidgetsModel(final Callbacks callbacks) { - final MultiHashMap<PackageItemInfo, WidgetItem> widgets - = mBgWidgetsModel.getWidgetsMap().clone(); - mHandler.post(new Runnable() { - @Override - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - callbacks.bindAllWidgets(widgets); - } - } - }); - } - - public void refreshAndBindWidgetsAndShortcuts(final Callbacks callbacks, - final boolean bindFirst, @Nullable final PackageUserKey packageUser) { - runOnWorkerThread(new Runnable() { + public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) { + enqueueModelUpdateTask(new ExtendedModelTask() { @Override - public void run() { - if (bindFirst && !mBgWidgetsModel.isEmpty()) { - bindWidgetsModel(callbacks); - } - ArrayList<WidgetItem> widgets = mBgWidgetsModel.update( - mApp.getContext(), packageUser); - bindWidgetsModel(callbacks); - - // update the Widget entries inside DB on the worker thread. - mApp.getWidgetCache().removeObsoletePreviews(widgets, packageUser); + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + dataModel.widgetsModel.update(app, packageUser); + bindUpdatedWidgets(dataModel); } }); } - static boolean isValidProvider(AppWidgetProviderInfo provider) { - return (provider != null) && (provider.provider != null) - && (provider.provider.getPackageName() != null); - } - public void dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { if (args.length > 0 && TextUtils.equals(args[0], "--all")) { writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size()); @@ -1964,23 +726,6 @@ public class LauncherModel extends BroadcastReceiver } /** - * @return {@link FolderInfo} if its already loaded. - */ - public FolderInfo findFolderById(Long folderId) { - synchronized (sBgDataModel) { - return sBgDataModel.folders.get(folderId); - } - } - - @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. */ public static Looper getWorkerLooper() { diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 3150d5b0e..4813571f5 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -16,6 +16,7 @@ package com.android.launcher3; +import android.annotation.TargetApi; import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetManager; import android.content.ComponentName; @@ -38,6 +39,7 @@ import android.database.sqlite.SQLiteQueryBuilder; import android.database.sqlite.SQLiteStatement; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -53,43 +55,39 @@ 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.graphics.IconShapeOverride; import com.android.launcher3.logging.FileLog; +import com.android.launcher3.model.DbDowngradeHelper; import com.android.launcher3.provider.LauncherDbUtils; +import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; import com.android.launcher3.provider.RestoreDbTask; import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.util.NoLocaleSqliteContext; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Thunk; +import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.lang.reflect.Method; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; public class LauncherProvider extends ContentProvider { private static final String TAG = "LauncherProvider"; private static final boolean LOGD = false; + private static final String DOWNGRADE_SCHEMA_FILE = "downgrade_schema.json"; + /** * Represents the schema of the database. Changes in scheme need not be backwards compatible. */ - private static final int SCHEMA_VERSION = 27; - /** - * Represents the actual data. It could include additional validations and normalizations added - * overtime. These must be backwards compatible, else we risk breaking old devices during - * restore or binary version downgrade. - */ - private static final int DATA_VERSION = 3; - - private static final String PREF_KEY_DATA_VERISON = "provider_data_version"; + public static final int SCHEMA_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"; @@ -114,7 +112,7 @@ public class LauncherProvider extends ContentProvider { @Override public boolean onCreate() { - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { Log.d(TAG, "Launcher process started"); } mListenerHandler = new Handler(mListenerWrapper); @@ -305,8 +303,7 @@ public class LauncherProvider extends ContentProvider { SqlArguments args = new SqlArguments(uri); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { + try (SQLiteTransaction t = new SQLiteTransaction(db)) { int numValues = values.length; for (int i = 0; i < numValues; i++) { addModifiedTime(values[i]); @@ -314,9 +311,7 @@ public class LauncherProvider extends ContentProvider { return 0; } } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); + t.commit(); } notifyListeners(); @@ -328,15 +323,11 @@ public class LauncherProvider extends ContentProvider { public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws OperationApplicationException { createDbIfNotExists(); - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { + try (SQLiteTransaction t = new SQLiteTransaction(mOpenHelper.getWritableDatabase())) { ContentProviderResult[] result = super.applyBatch(operations); - db.setTransactionSuccessful(); + t.commit(); reloadLauncherIfExternal(); return result; - } finally { - db.endTransaction(); } } @@ -442,31 +433,26 @@ public class LauncherProvider extends ContentProvider { private ArrayList<Long> deleteEmptyFolders() { ArrayList<Long> folderIds = new ArrayList<>(); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { + try (SQLiteTransaction t = new SQLiteTransaction(db)) { // Select folders whose id do not match any container value. String selection = LauncherSettings.Favorites.ITEM_TYPE + " = " + LauncherSettings.Favorites.ITEM_TYPE_FOLDER + " AND " + LauncherSettings.Favorites._ID + " NOT IN (SELECT " + LauncherSettings.Favorites.CONTAINER + " FROM " + Favorites.TABLE_NAME + ")"; - Cursor c = db.query(Favorites.TABLE_NAME, + try (Cursor c = db.query(Favorites.TABLE_NAME, new String[] {LauncherSettings.Favorites._ID}, - selection, null, null, null, null); - while (c.moveToNext()) { - folderIds.add(c.getLong(0)); + selection, null, null, null, null)) { + LauncherDbUtils.iterateCursor(c, 0, folderIds); } - c.close(); if (!folderIds.isEmpty()) { db.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery( LauncherSettings.Favorites._ID, folderIds), null); } - db.setTransactionSuccessful(); + t.commit(); } catch (SQLException ex) { Log.e(TAG, ex.getMessage(), ex); folderIds.clear(); - } finally { - db.endTransaction(); } return folderIds; } @@ -714,50 +700,30 @@ public class LauncherProvider extends ContentProvider { @Override public void onOpen(SQLiteDatabase db) { super.onOpen(db); - SharedPreferences prefs = mContext - .getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, 0); - int oldVersion = prefs.getInt(PREF_KEY_DATA_VERISON, 0); - if (oldVersion != DATA_VERSION) { - // Only run the data upgrade path for an existing db. - if (!Utilities.getPrefs(mContext).getBoolean(EMPTY_DATABASE_CREATED, false)) { - db.beginTransaction(); - try { - onDataUpgrade(db, oldVersion); - db.setTransactionSuccessful(); - } catch (Exception e) { - Log.d(TAG, "Error updating data version, ignoring", e); - return; - } finally { - db.endTransaction(); - } - } - prefs.edit().putInt(PREF_KEY_DATA_VERISON, DATA_VERSION).apply(); + + File schemaFile = mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE); + if (!schemaFile.exists()) { + handleOneTimeDataUpgrade(db); } + DbDowngradeHelper.updateSchemaFile(schemaFile, SCHEMA_VERSION, mContext, + R.raw.downgrade_schema); } /** - * Called when the data is updated as part of app update. It can be called multiple times - * with old version, even though it had been run before. The changes made here must be - * backwards compatible, else we risk breaking old devices during restore or binary - * version downgrade. + * One-time data updated before support of onDowngrade was added. This update is backwards + * compatible and can safely be run multiple times. + * Note: No new logic should be added here after release, as the new logic might not get + * executed on an existing device. + * TODO: Move this to db upgrade path, once the downgrade path is released. */ - protected void onDataUpgrade(SQLiteDatabase db, int oldVersion) { - switch (oldVersion) { - case 0: - case 1: { - // Remove "profile extra" - UserManagerCompat um = UserManagerCompat.getInstance(mContext); - for (UserHandle user : um.getUserProfiles()) { - long serial = um.getSerialNumberForUser(user); - String sql = "update favorites set intent = replace(intent, " - + "';l.profile=" + serial + ";', ';') where itemType = 0;"; - db.execSQL(sql); - } - } - case 2: - case 3: - // data updated - return; + protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { + // Remove "profile extra" + UserManagerCompat um = UserManagerCompat.getInstance(mContext); + for (UserHandle user : um.getUserProfiles()) { + long serial = um.getSerialNumberForUser(user); + String sql = "update favorites set intent = replace(intent, " + + "';l.profile=" + serial + ";', ';') where itemType = 0;"; + db.execSQL(sql); } } @@ -774,35 +740,29 @@ public class LauncherProvider extends ContentProvider { addWorkspacesTable(db, false); } case 13: { - db.beginTransaction(); - try { + try (SQLiteTransaction t = new SQLiteTransaction(db)) { // Insert new column for holding widget provider name db.execSQL("ALTER TABLE favorites " + "ADD COLUMN appWidgetProvider TEXT;"); - db.setTransactionSuccessful(); + t.commit(); } catch (SQLException ex) { Log.e(TAG, ex.getMessage(), ex); // Old version remains, which means we wipe old data break; - } finally { - db.endTransaction(); } } case 14: { - db.beginTransaction(); - try { + try (SQLiteTransaction t = new SQLiteTransaction(db)) { // Insert new column for holding update timestamp db.execSQL("ALTER TABLE favorites " + "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;"); db.execSQL("ALTER TABLE workspaceScreens " + "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;"); - db.setTransactionSuccessful(); + t.commit(); } catch (SQLException ex) { Log.e(TAG, ex.getMessage(), ex); // Old version remains, which means we wipe old data break; - } finally { - db.endTransaction(); } } case 15: { @@ -870,29 +830,25 @@ public class LauncherProvider extends ContentProvider { @Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion == 28 && newVersion == 27) { - // TODO: remove this check. This is only applicable for internal development/testing - // and for any released version of Launcher. - return; + try { + DbDowngradeHelper.parse(mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE)) + .onDowngrade(db, oldVersion, newVersion); + } catch (Exception e) { + Log.d(TAG, "Unable to downgrade from: " + oldVersion + " to " + newVersion + + ". Wiping databse.", e); + createEmptyDB(db); } - // This shouldn't happen -- throw our hands up in the air and start over. - Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion + - ". Wiping databse."); - createEmptyDB(db); } /** * Clears all the data for a fresh start. */ public void createEmptyDB(SQLiteDatabase db) { - db.beginTransaction(); - try { + try (SQLiteTransaction t = new SQLiteTransaction(db)) { 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(); + t.commit(); } } @@ -900,40 +856,39 @@ public class LauncherProvider extends ContentProvider { * Removes widgets which are registered to the Launcher's host, but are not present * in our model. */ + @TargetApi(Build.VERSION_CODES.O) public void removeGhostWidgets(SQLiteDatabase db) { // Get all existing widget ids. final AppWidgetHost host = newLauncherWidgetHost(); final int[] allWidgets; try { - Method getter = AppWidgetHost.class.getDeclaredMethod("getAppWidgetIds"); - getter.setAccessible(true); - allWidgets = (int[]) getter.invoke(host); - } catch (Exception e) { + // Although the method was defined in O, it has existed since the beginning of time, + // so it might work on older platforms as well. + allWidgets = host.getAppWidgetIds(); + } catch (IncompatibleClassChangeError e) { Log.e(TAG, "getAppWidgetIds not supported", e); return; } - try { - Cursor c = db.query(Favorites.TABLE_NAME, - new String[] {Favorites.APPWIDGET_ID }, - "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null, null, null); - HashSet<Integer> validWidgets = new HashSet<>(); + final HashSet<Integer> validWidgets = new HashSet<>(); + try (Cursor c = db.query(Favorites.TABLE_NAME, + new String[] {Favorites.APPWIDGET_ID }, + "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null, null, null)) { while (c.moveToNext()) { validWidgets.add(c.getInt(0)); } - c.close(); - - for (int widgetId : allWidgets) { - if (!validWidgets.contains(widgetId)) { - try { - FileLog.d(TAG, "Deleting invalid widget " + widgetId); - host.deleteAppWidgetId(widgetId); - } catch (RuntimeException e) { - // Ignore - } - } - } } catch (SQLException ex) { Log.w(TAG, "Error getting widgets list", ex); + return; + } + for (int widgetId : allWidgets) { + if (!validWidgets.contains(widgetId)) { + try { + FileLog.d(TAG, "Deleting invalid widget " + widgetId); + host.deleteAppWidgetId(widgetId); + } catch (RuntimeException e) { + // Ignore + } + } } } @@ -942,22 +897,16 @@ public class LauncherProvider extends ContentProvider { * launcher activity target with {@link Favorites#ITEM_TYPE_APPLICATION}. */ @Thunk void convertShortcutsToLauncherActivities(SQLiteDatabase db) { - db.beginTransaction(); - Cursor c = null; - SQLiteStatement updateStmt = null; - - try { - // Only consider the primary user as other users can't have a shortcut. - long userSerial = getDefaultUserSerial(); - c = db.query(Favorites.TABLE_NAME, new String[] { - Favorites._ID, - Favorites.INTENT, - }, "itemType=" + Favorites.ITEM_TYPE_SHORTCUT + " AND profileId=" + userSerial, - null, null, null, null); - - updateStmt = db.compileStatement("UPDATE favorites SET itemType=" - + Favorites.ITEM_TYPE_APPLICATION + " WHERE _id=?"); - + try (SQLiteTransaction t = new SQLiteTransaction(db); + // Only consider the primary user as other users can't have a shortcut. + Cursor c = db.query(Favorites.TABLE_NAME, + new String[] { Favorites._ID, Favorites.INTENT}, + "itemType=" + Favorites.ITEM_TYPE_SHORTCUT + + " AND profileId=" + getDefaultUserSerial(), + null, null, null, null); + SQLiteStatement updateStmt = db.compileStatement("UPDATE favorites SET itemType=" + + Favorites.ITEM_TYPE_APPLICATION + " WHERE _id=?") + ) { final int idIndex = c.getColumnIndexOrThrow(Favorites._ID); final int intentIndex = c.getColumnIndexOrThrow(Favorites.INTENT); @@ -979,17 +928,9 @@ public class LauncherProvider extends ContentProvider { updateStmt.bindLong(1, id); updateStmt.executeUpdateDelete(); } - db.setTransactionSuccessful(); + t.commit(); } catch (SQLException ex) { Log.w(TAG, "Error deduping shortcuts", ex); - } finally { - db.endTransaction(); - if (c != null) { - c.close(); - } - if (updateStmt != null) { - updateStmt.close(); - } } } @@ -997,26 +938,17 @@ public class LauncherProvider extends ContentProvider { * Recreates workspace table and migrates data to the new table. */ public boolean recreateWorkspaceTable(SQLiteDatabase db) { - db.beginTransaction(); - try { - Cursor c = db.query(WorkspaceScreens.TABLE_NAME, + try (SQLiteTransaction t = new SQLiteTransaction(db)) { + final ArrayList<Long> sortedIDs; + + try (Cursor c = db.query(WorkspaceScreens.TABLE_NAME, new String[] {LauncherSettings.WorkspaceScreens._ID}, null, null, null, null, - LauncherSettings.WorkspaceScreens.SCREEN_RANK); - ArrayList<Long> sortedIDs = new ArrayList<Long>(); - long maxId = 0; - try { - while (c.moveToNext()) { - Long id = c.getLong(0); - if (!sortedIDs.contains(id)) { - sortedIDs.add(id); - maxId = Math.max(maxId, id); - } - } - } finally { - c.close(); + LauncherSettings.WorkspaceScreens.SCREEN_RANK)) { + // Use LinkedHashSet so that ordering is preserved + sortedIDs = new ArrayList<>( + LauncherDbUtils.iterateCursor(c, 0, new LinkedHashSet<Long>())); } - db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME); addWorkspacesTable(db, false); @@ -1029,21 +961,18 @@ public class LauncherProvider extends ContentProvider { addModifiedTime(values); db.insertOrThrow(WorkspaceScreens.TABLE_NAME, null, values); } - db.setTransactionSuccessful(); - mMaxScreenId = maxId; + t.commit(); + mMaxScreenId = sortedIDs.isEmpty() ? 0 : Collections.max(sortedIDs); } catch (SQLException ex) { // Old version remains, which means we wipe old data Log.e(TAG, ex.getMessage(), ex); return false; - } finally { - db.endTransaction(); } return true; } @Thunk boolean updateFolderItemsRank(SQLiteDatabase db, boolean addRankColumn) { - db.beginTransaction(); - try { + try (SQLiteTransaction t = new SQLiteTransaction(db)) { if (addRankColumn) { // Insert new column for holding rank db.execSQL("ALTER TABLE favorites ADD COLUMN rank INTEGER NOT NULL DEFAULT 0;"); @@ -1062,13 +991,11 @@ public class LauncherProvider extends ContentProvider { } c.close(); - db.setTransactionSuccessful(); + t.commit(); } catch (SQLException ex) { // Old version remains, which means we wipe old data Log.e(TAG, ex.getMessage(), ex); return false; - } finally { - db.endTransaction(); } return true; } @@ -1078,16 +1005,13 @@ public class LauncherProvider extends ContentProvider { } private boolean addIntegerColumn(SQLiteDatabase db, String columnName, long defaultValue) { - db.beginTransaction(); - try { + try (SQLiteTransaction t = new SQLiteTransaction(db)) { db.execSQL("ALTER TABLE favorites ADD COLUMN " + columnName + " INTEGER NOT NULL DEFAULT " + defaultValue + ";"); - db.setTransactionSuccessful(); + t.commit(); } catch (SQLException ex) { Log.e(TAG, ex.getMessage(), ex); return false; - } finally { - db.endTransaction(); } return true; } diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index b25b256af..87f62eb01 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); } @@ -155,6 +153,18 @@ public class LauncherSettings { } } + static final String itemTypeToString(int type) { + switch(type) { + case ITEM_TYPE_APPLICATION: return "APP"; + case ITEM_TYPE_SHORTCUT: return "SHORTCUT"; + case ITEM_TYPE_FOLDER: return "FOLDER"; + case ITEM_TYPE_APPWIDGET: return "WIDGET"; + case ITEM_TYPE_CUSTOM_APPWIDGET: return "CUSTOMWIDGET"; + case ITEM_TYPE_DEEP_SHORTCUT: return "DEEPSHORTCUT"; + default: return String.valueOf(type); + } + } + /** * The screen holding the favorite (if container is CONTAINER_DESKTOP) * <P>Type: INTEGER</P> @@ -280,7 +290,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"; diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java index 39c466db8..85467e06a 100644 --- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java +++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java @@ -31,8 +31,8 @@ import android.view.animation.AccelerateInterpolator; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimationLayerSet; +import com.android.launcher3.anim.CircleRevealOutlineProvider; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.util.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/PagedView.java b/src/com/android/launcher3/PagedView.java index fb6a611e7..255677a53 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -50,6 +50,7 @@ import android.view.animation.Interpolator; import com.android.launcher3.anim.PropertyListBuilder; import com.android.launcher3.pageindicators.PageIndicator; import com.android.launcher3.util.LauncherEdgeEffect; +import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; import java.util.ArrayList; @@ -226,11 +227,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density); setOnHierarchyChangeListener(this); setWillNotDraw(false); - } - protected void setEdgeGlowColor(int color) { - mEdgeGlowLeft.setColor(color); - mEdgeGlowRight.setColor(color); + int edgeEffectColor = Themes.getAttrColor(getContext(), android.R.attr.colorEdgeEffect); + mEdgeGlowLeft.setColor(edgeEffectColor); + mEdgeGlowRight.setColor(edgeEffectColor); } protected void setDefaultInterpolator(Interpolator interpolator) { @@ -1599,7 +1599,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x); boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING && - Math.abs(velocityX) > mFlingThresholdVelocity; + shouldFlingForVelocity(velocityX); if (!mFreeScroll) { // In the case that the page is moved far to one direction and then is flung @@ -1705,6 +1705,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc return true; } + protected boolean shouldFlingForVelocity(int velocityX) { + return Math.abs(velocityX) > mFlingThresholdVelocity; + } + private void resetTouchState() { releaseVelocityTracker(); endReordering(); diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java index b163464dd..de424aba1 100644 --- a/src/com/android/launcher3/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/PendingAppWidgetHostView.java @@ -80,10 +80,13 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView updateAppWidget(null); setOnClickListener(mLauncher); - // Load icon - PackageItemInfo item = new PackageItemInfo(info.providerName.getPackageName()); - item.user = info.user; - cache.updateIconInBackground(this, item); + if (info.pendingItemInfo == null) { + info.pendingItemInfo = new PackageItemInfo(info.providerName.getPackageName()); + info.pendingItemInfo.user = info.user; + cache.updateIconInBackground(this, info.pendingItemInfo); + } else { + reapplyItemInfo(info.pendingItemInfo); + } } @Override diff --git a/src/com/android/launcher3/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java index f8196e5f6..c3d3bb3df 100644 --- a/src/com/android/launcher3/PinchAnimationManager.java +++ b/src/com/android/launcher3/PinchAnimationManager.java @@ -56,11 +56,10 @@ public class PinchAnimationManager { private static final LinearInterpolator INTERPOLATOR = new LinearInterpolator(); private static final int INDEX_HOTSEAT = 0; - private static final int INDEX_QSB = 1; - private static final int INDEX_OVERVIEW_PANEL_BUTTONS = 2; - private static final int INDEX_SCRIM = 3; + private static final int INDEX_OVERVIEW_PANEL_BUTTONS = 1; + private static final int INDEX_SCRIM = 2; - private final Animator[] mAnimators = new Animator[4]; + private final Animator[] mAnimators = new Animator[3]; private Launcher mLauncher; private Workspace mWorkspace; @@ -196,8 +195,6 @@ public class PinchAnimationManager { private void animateHotseatAndQsb(boolean show) { startAnimator(INDEX_HOTSEAT, mWorkspace.createHotseatAlphaAnimator(show ? 1 : 0), THRESHOLD_ANIM_DURATION); - startAnimator(INDEX_QSB, mWorkspace.mQsbAlphaController.animateAlphaAtIndex( - show ? 1 : 0, Workspace.QSB_ALPHA_INDEX_STATE_CHANGE), THRESHOLD_ANIM_DURATION); } private void animateOverviewPanelButtons(boolean show) { diff --git a/src/com/android/launcher3/PromiseAppInfo.java b/src/com/android/launcher3/PromiseAppInfo.java new file mode 100644 index 000000000..07515d08a --- /dev/null +++ b/src/com/android/launcher3/PromiseAppInfo.java @@ -0,0 +1,52 @@ +/* + * 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; + +import android.content.Intent; +import android.support.annotation.NonNull; + +import com.android.launcher3.compat.PackageInstallerCompat; +import com.android.launcher3.util.PackageManagerHelper; + +public class PromiseAppInfo extends AppInfo { + + public int level = 0; + + public PromiseAppInfo(@NonNull PackageInstallerCompat.PackageInstallInfo installInfo) { + componentName = installInfo.componentName; + intent = new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_LAUNCHER) + .setComponent(componentName) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + } + + @Override + public ShortcutInfo makeShortcut() { + ShortcutInfo shortcut = new ShortcutInfo(this); + shortcut.setInstallProgress(level); + // We need to update the component name when the apk is installed + shortcut.status |= ShortcutInfo.FLAG_AUTOINSTALL_ICON; + // Since the user is manually placing it on homescreen, it should not be auto-removed later + shortcut.status |= ShortcutInfo.FLAG_RESTORE_STARTED; + return shortcut; + } + + public Intent getMarketIntent() { + return PackageManagerHelper.getMarketIntent(componentName.getPackageName()); + } +} diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java index 61bcc178c..8caba75cd 100644 --- a/src/com/android/launcher3/SessionCommitReceiver.java +++ b/src/com/android/launcher3/SessionCommitReceiver.java @@ -67,18 +67,19 @@ public class SessionCommitReceiver extends BroadcastReceiver { SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION); UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); - if (TextUtils.isEmpty(info.getAppPackageName()) || - info.getInstallReason() != PackageManager.INSTALL_REASON_USER) { - return; + if (Process.myUserHandle().equals(user)) { + if (TextUtils.isEmpty(info.getAppPackageName()) || + info.getInstallReason() != PackageManager.INSTALL_REASON_USER) { + return; + } } - if (!Process.myUserHandle().equals(user)) { - // Managed profile is handled using ManagedProfileHeuristic - return; - } + queueAppIconAddition(context, info.getAppPackageName(), user); + } + public static void queueAppIconAddition(Context context, String packageName, UserHandle user) { List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(context) - .getActivityList(info.getAppPackageName(), user); + .getActivityList(packageName, user); if (activities == null || activities.isEmpty()) { // no activity found return; diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index 6f0417c08..f0d9367af 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -45,10 +45,10 @@ public class ShortcutInfo extends ItemInfoWithIcon { * be present along with {@link #FLAG_RESTORED_ICON}, and is set during default layout * parsing. */ - public static final int FLAG_AUTOINTALL_ICON = 2; //0B10; + public static final int FLAG_AUTOINSTALL_ICON = 2; //0B10; /** - * The icon is being installed. If {@link #FLAG_RESTORED_ICON} or {@link #FLAG_AUTOINTALL_ICON} + * The icon is being installed. If {@link #FLAG_RESTORED_ICON} or {@link #FLAG_AUTOINSTALL_ICON} * is set, then the icon is either being installed or is in a broken state. */ public static final int FLAG_INSTALL_SESSION_ACTIVE = 4; // 0B100; @@ -185,7 +185,7 @@ public class ShortcutInfo extends ItemInfoWithIcon { public final boolean isPromise() { - return hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINTALL_ICON); + return hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON); } public int getInstallProgress() { diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java index 0fac29f30..45c14d6bb 100644 --- a/src/com/android/launcher3/UninstallDropTarget.java +++ b/src/com/android/launcher3/UninstallDropTarget.java @@ -28,10 +28,13 @@ public class UninstallDropTarget extends ButtonDropTarget { @Override protected void onFinishInflate() { super.onFinishInflate(); + setupUi(); + } + + protected void setupUi() { // Get the hover color mHoverColor = getResources().getColor(R.color.uninstall_target_hover_tint); - - setDrawable(R.drawable.ic_uninstall_launcher); + setDrawable(R.drawable.ic_uninstall_shadow); } @Override diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index b5c44bb02..d48b9ab2c 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -28,6 +28,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; @@ -51,7 +52,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; @@ -260,7 +261,7 @@ public final class Utilities { return scale; } - static boolean isSystemApp(Context context, Intent intent) { + public static boolean isSystemApp(Context context, Intent intent) { PackageManager pm = context.getPackageManager(); ComponentName cn = intent.getComponent(); String packageName = null; @@ -550,6 +551,11 @@ public final class Utilities { LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE); } + public static SharedPreferences getDevicePrefs(Context context) { + return context.getSharedPreferences( + LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE); + } + public static boolean isPowerSaverOn(Context context) { PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); return powerManager.isPowerSaveMode(); @@ -571,7 +577,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); } } @@ -647,4 +653,28 @@ public final class Utilities { hashSet.add(elem); return hashSet; } + + /** + * @return creates a new alpha mask bitmap out of an existing bitmap + */ + public static Bitmap convertToAlphaMask(Bitmap b, int applyAlpha) { + Bitmap a = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ALPHA_8); + Canvas c = new Canvas(a); + Paint paint = new Paint(); + paint.setAlpha(applyAlpha); + c.drawBitmap(b, 0f, 0f, paint); + return a; + } + + /** + * @return a new white 1x1 bitmap with ALPHA_8 + */ + public static Bitmap createOnePixBitmap() { + Bitmap a = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); + Canvas c = new Canvas(a); + Paint paint = new Paint(); + paint.setColor(Color.WHITE); + c.drawPaint(paint); + return a; + } } diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index c525cd4bc..f66995f70 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -27,7 +27,6 @@ import android.os.CancellationSignal; import android.os.Handler; import android.os.UserHandle; import android.support.annotation.Nullable; -import android.support.v4.graphics.ColorUtils; import android.util.Log; import android.util.LongSparseArray; @@ -388,10 +387,10 @@ public class WidgetPreviewLoader { drawable.setBounds(x, 0, x + previewWidth, previewHeight); drawable.draw(c); } else { - final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); - RectF boxRect = drawBoxWithShadow(c, p, previewWidth, previewHeight); + RectF boxRect = drawBoxWithShadow(c, previewWidth, previewHeight); // Draw horizontal and vertical lines to represent individual columns. + final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); p.setStyle(Paint.Style.STROKE); p.setStrokeWidth(mContext.getResources() .getDimension(R.dimen.widget_preview_cell_divider_width)); @@ -431,7 +430,7 @@ public class WidgetPreviewLoader { return preview; } - private RectF drawBoxWithShadow(Canvas c, Paint p, int width, int height) { + private RectF drawBoxWithShadow(Canvas c, int width, int height) { Resources res = mContext.getResources(); float shadowBlur = res.getDimension(R.dimen.widget_preview_shadow_blur); float keyShadowDistance = res.getDimension(R.dimen.widget_preview_key_shadow_distance); @@ -439,19 +438,7 @@ public class WidgetPreviewLoader { RectF bounds = new RectF(shadowBlur, shadowBlur, width - shadowBlur, height - shadowBlur - keyShadowDistance); - p.setColor(Color.WHITE); - - // Key shadow - p.setShadowLayer(shadowBlur, 0, keyShadowDistance, - ShadowGenerator.KEY_SHADOW_ALPHA << 24); - c.drawRoundRect(bounds, corner, corner, p); - - // Ambient shadow - p.setShadowLayer(shadowBlur, 0, 0, - ColorUtils.setAlphaComponent(Color.BLACK, ShadowGenerator.AMBIENT_SHADOW_ALPHA)); - c.drawRoundRect(bounds, corner, corner, p); - - p.clearShadowLayer(); + ShadowGenerator.drawShadow(c, bounds, Color.WHITE, shadowBlur, keyShadowDistance, corner); return bounds; } @@ -478,8 +465,7 @@ public class WidgetPreviewLoader { c.setBitmap(preview); c.drawColor(0, PorterDuff.Mode.CLEAR); } - Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); - RectF boxRect = drawBoxWithShadow(c, p, size, size); + RectF boxRect = drawBoxWithShadow(c, size, size); Bitmap icon = LauncherIcons.createScaledBitmapWithoutShadow( mutateOnMainThread(info.getFullResIcon(mIconCache)), mContext, Build.VERSION_CODES.O); @@ -487,7 +473,8 @@ public class WidgetPreviewLoader { boxRect.set(0, 0, iconSize, iconSize); boxRect.offset(padding, padding); - c.drawBitmap(icon, src, boxRect, p); + c.drawBitmap(icon, src, boxRect, + new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG)); c.setBitmap(null); return preview; } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 36f2880cb..ce92c8eb0 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -43,6 +43,7 @@ import android.util.AttributeSet; import android.util.Log; import android.util.Property; import android.util.SparseArray; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug; @@ -64,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; @@ -80,7 +80,6 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LongArrayMap; -import com.android.launcher3.util.MultiStateAlphaController; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.VerticalFlingDetector; @@ -234,14 +233,6 @@ public class Workspace extends PagedView */ private float[] mHotseatAlpha = new float[] {1, 1, 1}; - public static final int QSB_ALPHA_INDEX_STATE_CHANGE = 0; - public static final int QSB_ALPHA_INDEX_Y_TRANSLATION = 1; - public static final int QSB_ALPHA_INDEX_PAGE_SCROLL = 2; - public static final int QSB_ALPHA_INDEX_OVERLAY_SCROLL = 3; - - - MultiStateAlphaController mQsbAlphaController; - @ViewDebug.ExportedProperty(category = "launcher") private State mState = State.NORMAL; private boolean mIsSwitchingState = false; @@ -323,7 +314,6 @@ public class Workspace extends PagedView private WorkspaceStateTransitionAnimation mStateTransitionAnimation; private AccessibilityDelegate mPagesAccessibilityDelegate; - private OnStateChangeListener mOnStateChangeListener; /** * Used to inflate the Workspace from XML. @@ -378,10 +368,6 @@ public class Workspace extends PagedView } } - public void setOnStateChangeListener(OnStateChangeListener listener) { - mOnStateChangeListener = listener; - } - /** * Estimates the size of an item using spans: hSpan, vSpan. * @@ -451,7 +437,7 @@ public class Workspace extends PagedView mLauncher.lockScreenOrientation(); mLauncher.onInteractionBegin(); // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging - InstallShortcutReceiver.enableInstallQueue(); + InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_DRAG_AND_DROP); // Do not add a new page if it is a accessible drag which was not started by the workspace. // We do not support accessibility drag from other sources and instead provide a direct @@ -504,7 +490,8 @@ public class Workspace extends PagedView mLauncher.unlockScreenOrientation(false); // Re-enable any Un/InstallShortcutReceiver and now process any queued items - InstallShortcutReceiver.disableAndFlushInstallQueue(getContext()); + InstallShortcutReceiver.disableAndFlushInstallQueue( + InstallShortcutReceiver.FLAG_DRAG_AND_DROP, getContext()); mOutlineProvider = null; mDragInfo = null; @@ -529,15 +516,12 @@ public class Workspace extends PagedView // Set the wallpaper dimensions when Launcher starts up setWallpaperDimension(); - - setEdgeGlowColor(getResources().getColor(R.color.workspace_edge_effect_color)); } @Override public void initParentViews(View parent) { super.initParentViews(parent); mPageIndicator.setAccessibilityDelegate(new OverviewAccessibilityDelegate()); - mQsbAlphaController = new MultiStateAlphaController(mLauncher.getQsbContainer(), 4); } private int getDefaultPage() { @@ -577,11 +561,6 @@ public class Workspace extends PagedView return mTouchState != TOUCH_STATE_REST; } - private int getEmbeddedQsbId() { - return mLauncher.getDeviceProfile().isVerticalBarLayout() - ? R.id.qsb_container : R.id.workspace_blocked_row; - } - /** * Initializes and binds the first page * @param qsb an existing qsb to recycle or null. @@ -623,41 +602,17 @@ public class Workspace extends PagedView if (qsb == null) { // In transposed layout, we add the QSB in the Grid. As workspace does not touch the // edges, we do not need a full width QSB. - qsb = mLauncher.getLayoutInflater().inflate( - mLauncher.getDeviceProfile().isVerticalBarLayout() - ? R.layout.qsb_container : R.layout.qsb_blocker_view, - firstPage, false); + qsb = LayoutInflater.from(getContext()) + .inflate(R.layout.qsb_container,firstPage, false); } CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(), 1); lp.canReorder = false; - if (!firstPage.addViewToCellLayout(qsb, 0, getEmbeddedQsbId(), lp, true)) { + if (!firstPage.addViewToCellLayout(qsb, 0, R.id.qsb_container, lp, true)) { Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout"); } } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - // Update the QSB to match the cell height. This is treating the QSB essentially as a child - // 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. - // 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(); - - View qsbContainer = mLauncher.getQsbContainer(); - ViewGroup.LayoutParams lp = qsbContainer.getLayoutParams(); - if (cellHeight > 0 && lp.height != cellHeight) { - lp.height = cellHeight; - qsbContainer.setLayoutParams(lp); - } - } - } - public void removeAllWorkspaceScreens() { // Disable all layout transitions before removing all pages to ensure that we don't get the // transition animations competing with us changing the scroll when we add pages or the @@ -671,7 +626,7 @@ public class Workspace extends PagedView } // Recycle the QSB widget - View qsb = findViewById(getEmbeddedQsbId()); + View qsb = findViewById(R.id.qsb_container); if (qsb != null) { ((ViewGroup) qsb.getParent()).removeView(qsb); } @@ -709,7 +664,7 @@ public class Workspace extends PagedView // Inflate the cell layout, but do not add it automatically so that we can get the newly // created CellLayout. - CellLayout newScreen = (CellLayout) mLauncher.getLayoutInflater().inflate( + CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate( R.layout.workspace_screen, this, false /* attachToRoot */); newScreen.setOnLongClickListener(mLongClickListener); newScreen.setOnClickListener(mLauncher); @@ -727,7 +682,7 @@ public class Workspace extends PagedView public void createCustomContentContainer() { CellLayout customScreen = (CellLayout) - mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, this, false); + LayoutInflater.from(getContext()).inflate(R.layout.workspace_screen, this, false); customScreen.disableDragTarget(); customScreen.disableJailContent(); @@ -1407,17 +1362,9 @@ public class Workspace extends PagedView super.scrollTo(x, y); } - private void onWorkspaceOverallScrollChanged() { - if (!mIgnoreQsbScroll) { - mLauncher.getQsbContainer().setTranslationX( - mOverlayTranslation + mFirstPageScrollX - getScrollX()); - } - } - @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); - onWorkspaceOverallScrollChanged(); // Update the page indicator progress. boolean isTransitioning = mIsSwitchingState @@ -1465,6 +1412,13 @@ public class Workspace extends PagedView } } + @Override + protected boolean shouldFlingForVelocity(int velocityX) { + // When the overlay is moving, the fling or settle transition is controlled by the overlay. + return Float.compare(mOverlayTranslation, 0) == 0 && + super.shouldFlingForVelocity(velocityX); + } + private final Interpolator mAlphaInterpolator = new DecelerateInterpolator(3f); /** @@ -1491,9 +1445,6 @@ public class Workspace extends PagedView // device I've tried, translating the launcher causes things to get quite laggy. setWorkspaceTranslationAndAlpha(Direction.X, transX, alpha); setHotseatTranslationAndAlpha(Direction.X, transX, alpha); - onWorkspaceOverallScrollChanged(); - - mQsbAlphaController.setAlphaAtIndex(alpha, QSB_ALPHA_INDEX_OVERLAY_SCROLL); } /** @@ -1503,9 +1454,6 @@ public class Workspace extends PagedView */ public void setWorkspaceYTranslationAndAlpha(float translation, float alpha) { setWorkspaceTranslationAndAlpha(Direction.Y, translation, alpha); - - mLauncher.getQsbContainer().setTranslationY(translation); - mQsbAlphaController.setAlphaAtIndex(alpha, QSB_ALPHA_INDEX_Y_TRANSLATION); } /** @@ -1701,10 +1649,6 @@ public class Workspace extends PagedView float scrollProgress = getScrollProgress(screenCenter, child, i); float alpha = 1 - Math.abs(scrollProgress); child.getShortcutsAndWidgets().setAlpha(alpha); - - if (isQsbContainerPage(i)) { - mQsbAlphaController.setAlphaAtIndex(alpha, QSB_ALPHA_INDEX_PAGE_SCROLL); - } } } } @@ -1799,7 +1743,6 @@ public class Workspace extends PagedView } super.onLayout(changed, left, top, right, bottom); mFirstPageScrollX = getScrollForPage(0); - onWorkspaceOverallScrollChanged(); final LayoutTransition transition = getLayoutTransition(); // If the transition is running defer updating max scroll, as some empty pages could @@ -1821,7 +1764,6 @@ public class Workspace extends PagedView mIgnoreQsbScroll = false; transition.removeTransitionListener(this); mFirstPageScrollX = getScrollForPage(0); - onWorkspaceOverallScrollChanged(); } } }); @@ -2064,10 +2006,6 @@ public class Workspace extends PagedView mLauncher.notifyWidgetProvidersChanged(); } - if (mOnStateChangeListener != null) { - mOnStateChangeListener.prepareStateChange(toState, animated ? workspaceAnim : null); - } - onPrepareStateTransition(mState.hasMultipleVisiblePages); StateTransitionListener listener = new StateTransitionListener(); @@ -2620,7 +2558,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], @@ -2953,7 +2891,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; @@ -3160,11 +3098,8 @@ public class Workspace extends PagedView this.cellX = cellX; this.cellY = cellY; - DeviceProfile grid = mLauncher.getDeviceProfile(); BubbleTextView cell = (BubbleTextView) layout.getChildAt(cellX, cellY); - - bg.setup(getResources().getDisplayMetrics(), grid, null, - cell.getMeasuredWidth(), cell.getPaddingTop()); + bg.setup(mLauncher, null, cell.getMeasuredWidth(), cell.getPaddingTop()); // The full preview background should appear behind the icon bg.isClipping = false; @@ -3610,7 +3545,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. "); }; @@ -3636,7 +3571,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 @@ -4227,20 +4162,6 @@ public class Workspace extends PagedView } } - public interface OnStateChangeListener { - - /** - * Called when the workspace state is changing. - * @param toState final state - * @param targetAnim animation which will be played during the transition or null. - */ - void prepareStateChange(State toState, AnimatorSet targetAnim); - } - - public static final boolean isQsbContainerPage(int pageNo) { - return pageNo == 0; - } - private class StateTransitionListener extends AnimatorListenerAdapter implements AnimatorUpdateListener { @Override diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java index 482a2c93b..32deaf286 100644 --- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java +++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java @@ -355,27 +355,10 @@ public class WorkspaceStateTransitionAnimation { cl.setBackgroundAlpha(finalBackgroundAlpha); cl.setShortcutAndWidgetAlpha(finalAlpha); } - - if (Workspace.isQsbContainerPage(i) && - states.stateIsNormal && mWorkspaceFadeInAdjacentScreens) { - if (animated) { - Animator anim = mWorkspace.mQsbAlphaController - .animateAlphaAtIndex(finalAlpha, Workspace.QSB_ALPHA_INDEX_PAGE_SCROLL); - anim.setDuration(duration); - anim.setInterpolator(mZoomInInterpolator); - mStateAnimator.play(anim); - } else { - mWorkspace.mQsbAlphaController.setAlphaAtIndex( - finalAlpha, Workspace.QSB_ALPHA_INDEX_PAGE_SCROLL); - } - } } final ViewGroup overviewPanel = mLauncher.getOverviewPanel(); - Animator qsbAlphaAnimation = mWorkspace.mQsbAlphaController - .animateAlphaAtIndex(finalQsbAlpha, Workspace.QSB_ALPHA_INDEX_STATE_CHANGE); - if (animated) { Animator scale = LauncherAnimUtils.ofPropertyValuesHolder(mWorkspace, new PropertyListBuilder().scale(mNewScale) @@ -393,7 +376,6 @@ public class WorkspaceStateTransitionAnimation { // For animation optimization, we may need to provide the Launcher transition // with a set of views on which to force build and manage layers in certain scenarios. layerViews.addView(overviewPanel); - layerViews.addView(mLauncher.getQsbContainer()); layerViews.addView(mLauncher.getHotseat()); layerViews.addView(mWorkspace.getPageIndicator()); @@ -407,11 +389,9 @@ public class WorkspaceStateTransitionAnimation { overviewPanelAlpha.setDuration(duration); hotseatAlpha.setDuration(duration); - qsbAlphaAnimation.setDuration(duration); mStateAnimator.play(overviewPanelAlpha); mStateAnimator.play(hotseatAlpha); - mStateAnimator.play(qsbAlphaAnimation); mStateAnimator.addListener(new AnimatorListenerAdapter() { boolean canceled = false; @Override @@ -439,7 +419,6 @@ public class WorkspaceStateTransitionAnimation { AlphaUpdateListener.updateVisibility(overviewPanel, accessibilityEnabled); mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded); - qsbAlphaAnimation.end(); mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha).end(); mWorkspace.updateCustomContentVisibility(); mWorkspace.setScaleX(mNewScale); diff --git a/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java index d271f1d4e..9c23c1980 100644 --- a/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java +++ b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java @@ -17,8 +17,8 @@ package com.android.launcher3.accessibility; import com.android.launcher3.CellLayout; -import com.android.launcher3.folder.FolderPagedView; import com.android.launcher3.R; +import com.android.launcher3.folder.FolderPagedView; /** * Implementation of {@link DragAndDropAccessibilityDelegate} to support DnD in a folder. diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 70e578163..34335330b 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -18,7 +18,6 @@ import com.android.launcher3.AppInfo; import com.android.launcher3.AppWidgetResizeFrame; import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; -import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.FolderInfo; @@ -27,7 +26,6 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetHostView; import com.android.launcher3.LauncherAppWidgetInfo; -import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; import com.android.launcher3.PendingAddItemInfo; import com.android.launcher3.R; @@ -37,6 +35,7 @@ import com.android.launcher3.Workspace; import com.android.launcher3.dragndrop.DragController.DragListener; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.folder.Folder; +import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.util.Thunk; diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java index 9a23aa813..e6f120fe6 100644 --- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java +++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java @@ -27,9 +27,9 @@ import com.android.launcher3.CellLayout; import com.android.launcher3.FolderInfo; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; -import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DragType; import com.android.launcher3.R; import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DragType; import com.android.launcher3.dragndrop.DragLayer; /** diff --git a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java index c71bc3166..54c5bd0b2 100644 --- a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java +++ b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java @@ -25,6 +25,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.Gravity; +import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.R; /** @@ -119,7 +120,8 @@ public class AllAppsBackgroundDrawable extends Drawable { int finalAlphaI = (int) (finalAlpha * 255f); if (getAlpha() != finalAlphaI) { mBackgroundAnim = cancelAnimator(mBackgroundAnim); - mBackgroundAnim = ObjectAnimator.ofInt(this, "alpha", finalAlphaI); + mBackgroundAnim = ObjectAnimator.ofInt(this, LauncherAnimUtils.DRAWABLE_ALPHA, + finalAlphaI); mBackgroundAnim.setDuration(duration); mBackgroundAnim.start(); } diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 2c7d15629..c3df07360 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -20,15 +20,10 @@ 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.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.Selection; -import android.text.Spannable; -import android.text.SpannableString; import android.text.SpannableStringBuilder; -import android.text.TextUtils; -import android.text.method.TextKeyListener; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; @@ -42,25 +37,22 @@ import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; -import com.android.launcher3.ExtendedEditText; import com.android.launcher3.Insettable; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; +import com.android.launcher3.PromiseAppInfo; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.anim.SpringAnimationHandler; 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; -import com.android.launcher3.graphics.TintedDrawableSpan; import com.android.launcher3.keyboard.FocusedItemDecorator; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageUserKey; -import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -68,26 +60,24 @@ import java.util.Set; * The all apps view container. */ public class AllAppsContainerView extends BaseContainerView implements DragSource, - View.OnLongClickListener, AllAppsSearchBarController.Callbacks, Insettable { + View.OnLongClickListener, Insettable { private final Launcher mLauncher; private final AlphabeticalAppsList mApps; private final AllAppsGridAdapter mAdapter; - private final RecyclerView.LayoutManager mLayoutManager; + private final LinearLayoutManager mLayoutManager; private AllAppsRecyclerView mAppsRecyclerView; - private AllAppsSearchBarController mSearchBarController; - + private SearchUiManager mSearchUiManager; private View mSearchContainer; - private int mSearchContainerMinHeight; - private ExtendedEditText mSearchInput; - private HeaderElevationController mElevationController; private SpannableStringBuilder mSearchQueryBuilder = null; private int mNumAppsPerRow; private int mNumPredictedAppsPerRow; + private SpringAnimationHandler mSpringAnimationHandler; + public AllAppsContainerView(Context context) { this(context, null); } @@ -102,11 +92,10 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mLauncher = Launcher.getLauncher(context); mApps = new AlphabeticalAppsList(context); mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this); + mSpringAnimationHandler = mAdapter.getSpringAnimationHandler(); mApps.setAdapter(mAdapter); mLayoutManager = mAdapter.getLayoutManager(); mSearchQueryBuilder = new SpannableStringBuilder(); - mSearchContainerMinHeight - = getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_height); Selection.setSelection(mSearchQueryBuilder, 0); } @@ -148,7 +137,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc */ public void addApps(List<AppInfo> apps) { mApps.addApps(apps); - mSearchBarController.refreshSearchResult(); + mSearchUiManager.refreshSearchResult(); } /** @@ -156,42 +145,26 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc */ public void updateApps(List<AppInfo> apps) { mApps.updateApps(apps); - mSearchBarController.refreshSearchResult(); - } - - /** - * Removes some apps from the list. - */ - public void removeApps(List<AppInfo> apps) { - mApps.removeApps(apps); - mSearchBarController.refreshSearchResult(); + mSearchUiManager.refreshSearchResult(); } - public void setSearchBarVisible(boolean visible) { - if (visible) { - mSearchBarController.setVisibility(View.VISIBLE); - } else { - mSearchBarController.setVisibility(View.INVISIBLE); - } - } - - /** - * Sets the search bar that shows above the a-z list. - */ - public void setSearchBarController(AllAppsSearchBarController searchController) { - if (mSearchBarController != null) { - throw new RuntimeException("Expected search bar controller to only be set once"); + public void updatePromiseAppProgress(PromiseAppInfo app) { + int childCount = mAppsRecyclerView.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = mAppsRecyclerView.getChildAt(i); + if (child instanceof BubbleTextView && child.getTag() == app) { + BubbleTextView bubbleTextView = (BubbleTextView) child; + bubbleTextView.applyProgressLevel(app.level); + } } - mSearchBarController = searchController; - mSearchBarController.initialize(mApps, mSearchInput, mLauncher, this); - mAdapter.setSearchController(mSearchBarController); } /** - * Scrolls this list view to the top. + * Removes some apps from the list. */ - public void scrollToTop() { - mAppsRecyclerView.scrollToTop(); + public void removeApps(List<AppInfo> apps) { + mApps.removeApps(apps); + mSearchUiManager.refreshSearchResult(); } /** @@ -226,9 +199,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc * Focuses the search field and begins an app search. */ public void startAppsSearch() { - if (mSearchBarController != null) { - mSearchBarController.focusSearchField(); - } + mSearchUiManager.startAppsSearch(); } /** @@ -236,9 +207,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc */ public void reset() { // Reset the search bar and base recycler view after transitioning home - scrollToTop(); - mSearchBarController.reset(); - mAppsRecyclerView.reset(); + mAppsRecyclerView.scrollToTop(); + mSearchUiManager.reset(); } @Override @@ -256,28 +226,21 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } }); - mSearchContainer = findViewById(R.id.search_container); - mSearchInput = (ExtendedEditText) findViewById(R.id.search_box_input); - - // Update the hint to contain the icon. - // Prefix the original hint with two spaces. The first space gets replaced by the icon - // using span. The second space is used for a singe space character between the hint - // and the icon. - SpannableString spanned = new SpannableString(" " + mSearchInput.getHint()); - spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search), - 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); - mSearchInput.setHint(spanned); - - mElevationController = new HeaderElevationController(mSearchContainer); - // Load the all apps recycler view mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view); mAppsRecyclerView.setApps(mApps); mAppsRecyclerView.setLayoutManager(mLayoutManager); mAppsRecyclerView.setAdapter(mAdapter); mAppsRecyclerView.setHasFixedSize(true); - mAppsRecyclerView.addOnScrollListener(mElevationController); - mAppsRecyclerView.setElevationController(mElevationController); + if (FeatureFlags.LAUNCHER3_PHYSICS) { + mAppsRecyclerView.setSpringAnimationHandler(mSpringAnimationHandler); + mAppsRecyclerView.addOnScrollListener(new SpringMotionOnScrollListener()); + } + + mSearchContainer = findViewById(R.id.search_container); + mSearchUiManager = (SearchUiManager) mSearchContainer; + mSearchUiManager.initialize(mApps, mAppsRecyclerView); + FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView); mAppsRecyclerView.addItemDecoration(focusedItemDecorator); @@ -291,18 +254,21 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } } + public SearchUiManager getSearchUiManager() { + return mSearchUiManager; + } + @Override public View getTouchDelegateTargetView() { return mAppsRecyclerView; } @Override - public void onBoundsChanged(Rect newBounds) { } - - @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { DeviceProfile grid = mLauncher.getDeviceProfile(); + // Update the number of items in the grid before we measure the view grid.updateAppsViewNumCols(); + if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { if (mNumAppsPerRow != grid.inv.numColumns || mNumPredictedAppsPerRow != grid.inv.numColumns) { @@ -313,22 +279,11 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mAdapter.setNumAppsPerRow(mNumAppsPerRow); mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow); } - if (!grid.isVerticalBarLayout()) { - MarginLayoutParams searchContainerLp = - (MarginLayoutParams) mSearchContainer.getLayoutParams(); - - searchContainerLp.height = mLauncher.getDragLayer().getInsets().top - + mSearchContainerMinHeight; - mSearchContainer.setLayoutParams(searchContainerLp); - } super.onMeasure(widthMeasureSpec, heightMeasureSpec); return; } // --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. --- - - // Update the number of items in the grid before we measure the view - grid.updateAppsViewNumCols(); if (mNumAppsPerRow != grid.allAppsNumCols || mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) { mNumAppsPerRow = grid.allAppsNumCols; @@ -345,22 +300,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc @Override public boolean dispatchKeyEvent(KeyEvent event) { - // Determine if the key event was actual text, if so, focus the search bar and then dispatch - // the key normally so that it can process this key event - if (!mSearchBarController.isSearchFieldFocused() && - event.getAction() == KeyEvent.ACTION_DOWN) { - final int unicodeChar = event.getUnicodeChar(); - final boolean isKeyNotWhitespace = unicodeChar > 0 && - !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar); - if (isKeyNotWhitespace) { - boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder, - event.getKeyCode(), event); - if (gotKey && mSearchQueryBuilder.length() > 0) { - mSearchBarController.focusSearchField(); - } - } - } - + mSearchUiManager.preDispatchKeyEvent(event); return super.dispatchKeyEvent(event); } @@ -428,47 +368,21 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } @Override - public void onSearchResult(String query, ArrayList<ComponentKey> apps) { - if (apps != null) { - 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(); - } - - // Clear the search query - mSearchQueryBuilder.clear(); - mSearchQueryBuilder.clearSpans(); - Selection.setSelection(mSearchQueryBuilder, 0); - } - - @Override public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) { targetParent.containerType = mAppsRecyclerView.getContainerType(v); } public boolean shouldRestoreImeState() { - return !TextUtils.isEmpty(mSearchInput.getText()); + return mSearchUiManager.shouldRestoreImeState(); } @Override public void setInsets(Rect insets) { DeviceProfile grid = mLauncher.getDeviceProfile(); + mAppsRecyclerView.setPadding( + mAppsRecyclerView.getPaddingLeft(), mAppsRecyclerView.getPaddingTop(), + mAppsRecyclerView.getPaddingRight(), insets.bottom); + if (grid.isVerticalBarLayout()) { ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams(); mlp.leftMargin = insets.left; @@ -480,7 +394,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams(); navBarBgLp.height = insets.bottom; navBarBg.setLayoutParams(navBarBgLp); - navBarBg.setVisibility(View.VISIBLE); + navBarBg.setVisibility(FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS + ? View.INVISIBLE : View.VISIBLE); } } @@ -498,4 +413,39 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } } } + + public SpringAnimationHandler getSpringAnimationHandler() { + return mSpringAnimationHandler; + } + + public class SpringMotionOnScrollListener extends RecyclerView.OnScrollListener { + + private int mScrollState = RecyclerView.SCROLL_STATE_IDLE; + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + if (mScrollState == RecyclerView.SCROLL_STATE_DRAGGING + || (dx == 0 && dy == 0)) { + if (mSpringAnimationHandler.isRunning()){ + mSpringAnimationHandler.skipToEnd(); + } + return; + } + + int first = mLayoutManager.findFirstVisibleItemPosition(); + int last = mLayoutManager.findLastVisibleItemPosition(); + + // We only show the spring animation when at the top or bottom, so we wait until the + // first or last row is visible to ensure that all animations run in sync. + if ((first == 0 && dy < 0) || (last == mAdapter.getItemCount() - 1 && dy > 0)) { + mSpringAnimationHandler.animateToFinalPosition(0); + } + } + + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + mScrollState = newState; + } + } } diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java index a1ff8223a..e08cb15cd 100644 --- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java +++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java @@ -16,11 +16,7 @@ package com.android.launcher3.allapps; 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; import java.util.HashSet; @@ -210,7 +206,9 @@ public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallb for (RecyclerView.ViewHolder viewHolder : mTrackedFastScrollViews) { int pos = viewHolder.getAdapterPosition(); boolean isActive = false; - if (mCurrentFastScrollSection != null && pos > -1) { + if (mCurrentFastScrollSection != null + && pos > RecyclerView.NO_POSITION + && pos < mApps.getAdapterItems().size()) { AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos); isActive = item != null && mCurrentFastScrollSection.equals(item.sectionName) && diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 59cac8d26..9c7372f2c 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -19,6 +19,8 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Point; +import android.support.animation.DynamicAnimation; +import android.support.animation.SpringAnimation; import android.support.v4.view.accessibility.AccessibilityEventCompat; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; import android.support.v4.view.accessibility.AccessibilityRecordCompat; @@ -33,13 +35,17 @@ 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.Launcher; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem; +import com.android.launcher3.anim.SpringAnimationHandler; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.discovery.AppDiscoveryAppInfo; import com.android.launcher3.discovery.AppDiscoveryItemView; +import com.android.launcher3.util.PackageManagerHelper; import java.util.List; @@ -79,6 +85,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. | VIEW_TYPE_PREDICTION_ICON; public static final int VIEW_TYPE_MASK_CONTENT = VIEW_TYPE_MASK_ICON | VIEW_TYPE_DISCOVERY_ITEM; + public static final int VIEW_TYPE_MASK_HAS_SPRINGS = VIEW_TYPE_MASK_ICON + | VIEW_TYPE_PREDICTION_DIVIDER; public interface BindViewCallback { @@ -89,6 +97,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. * ViewHolder for each icon. */ public static class ViewHolder extends RecyclerView.ViewHolder { + public ViewHolder(View v) { super(v); } @@ -160,11 +169,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. } return extraRows; } - - @Override - public int getPaddingBottom() { - return mLauncher.getDragLayer().getInsets().bottom; - } } /** @@ -199,7 +203,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. private int mAppsPerRow; private BindViewCallback mBindViewCallback; - private AllAppsSearchBarController mSearchController; private OnFocusChangeListener mIconFocusListener; // The text to show when there are no search results and no market search handler. @@ -207,6 +210,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. // The intent to send off to the market app, updated each time the search query changes. private Intent mMarketSearchIntent; + private SpringAnimationHandler<ViewHolder> mSpringAnimationHandler; + public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps, View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener) { Resources res = launcher.getResources(); @@ -219,6 +224,14 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. mLayoutInflater = LayoutInflater.from(launcher); mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; + if (FeatureFlags.LAUNCHER3_PHYSICS) { + mSpringAnimationHandler = new SpringAnimationHandler<>( + SpringAnimationHandler.Y_DIRECTION, new AllAppsSpringAnimationFactory()); + } + } + + public SpringAnimationHandler getSpringAnimationHandler() { + return mSpringAnimationHandler; } public static boolean isDividerViewType(int viewType) { @@ -241,8 +254,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. mGridLayoutMgr.setSpanCount(appsPerRow); } - public void setSearchController(AllAppsSearchBarController searchController) { - mSearchController = searchController; + public int getNumAppsPerRow() { + return mAppsPerRow; } public void setIconFocusListener(OnFocusChangeListener focusListener) { @@ -256,7 +269,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. public void setLastSearchQuery(String query) { Resources res = mLauncher.getResources(); mEmptySearchMessage = res.getString(R.string.all_apps_no_search_results, query); - mMarketSearchIntent = mSearchController.createMarketSearchIntent(query); + mMarketSearchIntent = PackageManagerHelper.getMarketSearchIntent(mLauncher, query); } /** @@ -282,8 +295,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. R.layout.all_apps_icon, parent, false); icon.setOnClickListener(mIconClickListener); icon.setOnLongClickListener(mIconLongClickListener); - icon.setLongPressTimeout(ViewConfiguration.get(parent.getContext()) - .getLongPressTimeout()); + icon.setLongPressTimeout(ViewConfiguration.getLongPressTimeout()); icon.setOnFocusChangeListener(mIconFocusListener); // Ensure the all apps icon height matches the workspace icons @@ -336,7 +348,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. AppInfo info = mApps.getAdapterItems().get(position).appInfo; BubbleTextView icon = (BubbleTextView) holder.itemView; icon.applyFromApplicationInfo(info); - icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate()); break; case VIEW_TYPE_DISCOVERY_ITEM: AppDiscoveryAppInfo appDiscoveryAppInfo = (AppDiscoveryAppInfo) @@ -374,6 +385,22 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. } @Override + public void onViewAttachedToWindow(ViewHolder holder) { + int type = holder.getItemViewType(); + if (FeatureFlags.LAUNCHER3_PHYSICS && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) { + mSpringAnimationHandler.add(holder.itemView, holder); + } + } + + @Override + public void onViewDetachedFromWindow(ViewHolder holder) { + int type = holder.getItemViewType(); + if (FeatureFlags.LAUNCHER3_PHYSICS && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) { + mSpringAnimationHandler.remove(holder.itemView); + } + } + + @Override public boolean onFailedToRecycleView(ViewHolder holder) { // Always recycle and we will reset the view when it is bound return true; @@ -389,4 +416,121 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position); return item.viewType; } + + /** + * Helper class to set the SpringAnimation values for an item in the adapter. + */ + private class AllAppsSpringAnimationFactory + implements SpringAnimationHandler.AnimationFactory<ViewHolder> { + private static final float DEFAULT_MAX_VALUE_PX = 100; + private static final float DEFAULT_MIN_VALUE_PX = -DEFAULT_MAX_VALUE_PX; + + // Damping ratio range is [0, 1] + private static final float SPRING_DAMPING_RATIO = 0.55f; + + // Stiffness is a non-negative number. + private static final float MIN_SPRING_STIFFNESS = 580f; + private static final float MAX_SPRING_STIFFNESS = 900f; + + // The amount by which each adjacent rows' stiffness will differ. + private static final float ROW_STIFFNESS_COEFFICIENT = 50f; + + @Override + public SpringAnimation initialize(ViewHolder vh) { + return SpringAnimationHandler.forView(vh.itemView, DynamicAnimation.TRANSLATION_Y, 0); + } + + /** + * @param spring A new or recycled SpringAnimation. + * @param vh The ViewHolder that {@param spring} is related to. + */ + @Override + public void update(SpringAnimation spring, ViewHolder vh) { + int numPredictedApps = Math.min(mAppsPerRow, mApps.getPredictedApps().size()); + int appPosition = getAppPosition(vh.getAdapterPosition(), numPredictedApps, + mAppsPerRow); + + int col = appPosition % mAppsPerRow; + int row = appPosition / mAppsPerRow; + + int numTotalRows = mApps.getNumAppRows() - 1; // zero-based count + if (row > (numTotalRows / 2)) { + // Mirror the rows so that the top row acts the same as the bottom row. + row = Math.abs(numTotalRows - row); + } + + // We manipulate the stiffness, min, and max values based on the items distance to the + // first row and the items distance to the center column to create the ^-shaped motion + // effect. + float rowFactor = (1 + row) * 0.5f; + float colFactor = getColumnFactor(col, mAppsPerRow); + + float minValue = DEFAULT_MIN_VALUE_PX * (rowFactor + colFactor); + float maxValue = DEFAULT_MAX_VALUE_PX * (rowFactor + colFactor); + + float stiffness = Utilities.boundToRange( + MAX_SPRING_STIFFNESS - (row * ROW_STIFFNESS_COEFFICIENT), + MIN_SPRING_STIFFNESS, + MAX_SPRING_STIFFNESS); + + spring.setMinValue(minValue) + .setMaxValue(maxValue) + .getSpring() + .setStiffness(stiffness) + .setDampingRatio(SPRING_DAMPING_RATIO); + } + + /** + * @return The app position is the position of the app in the Adapter if we ignored all + * other view types. + * + * The first app is at position 0, and the first app each following row is at a + * position that is a multiple of {@param appsPerRow}. + * + * ie. If there are 5 apps per row, and there are two rows of apps: + * 0 1 2 3 4 + * 5 6 7 8 9 + */ + private int getAppPosition(int position, int numPredictedApps, int appsPerRow) { + int appPosition = position; + int numDividerViews = 1 + (numPredictedApps == 0 ? 0 : 1); + + int allAppsStartAt = numDividerViews + numPredictedApps; + if (numDividerViews == 1 || position < allAppsStartAt) { + appPosition -= 1; + } else { + // We cannot assume that the predicted row will always be full. + int numPredictedAppsOffset = appsPerRow - numPredictedApps; + appPosition = position + numPredictedAppsOffset - numDividerViews; + } + + return appPosition; + } + + /** + * Increase the column factor as the distance increases between the column and the center + * column(s). + */ + private float getColumnFactor(int col, int numCols) { + float centerColumn = numCols / 2; + int distanceToCenter = (int) Math.abs(col - centerColumn); + + boolean evenNumberOfColumns = numCols % 2 == 0; + if (evenNumberOfColumns && col < centerColumn) { + distanceToCenter -= 1; + } + + float factor = 0; + while (distanceToCenter > 0) { + if (distanceToCenter == 1) { + factor += 0.2f; + } else { + factor += 0.1f; + } + --distanceToCenter; + } + + return factor; + } + } } diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java index 64e2fcb3d..b2a74ff15 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java @@ -28,8 +28,8 @@ import android.view.View; import com.android.launcher3.BaseRecyclerView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.Launcher; import com.android.launcher3.R; +import com.android.launcher3.anim.SpringAnimationHandler; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.graphics.DrawableFactory; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; @@ -53,7 +53,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView { private AllAppsBackgroundDrawable mEmptySearchBackground; private int mEmptySearchBackgroundTopOffset; - private HeaderElevationController mElevationController; + private SpringAnimationHandler mSpringAnimationHandler; public AllAppsRecyclerView(Context context) { this(context, null); @@ -77,6 +77,18 @@ public class AllAppsRecyclerView extends BaseRecyclerView { R.dimen.all_apps_empty_search_bg_top_offset); } + public void setSpringAnimationHandler(SpringAnimationHandler springAnimationHandler) { + mSpringAnimationHandler = springAnimationHandler; + } + + @Override + public boolean onTouchEvent(MotionEvent e) { + if (FeatureFlags.LAUNCHER3_PHYSICS && mSpringAnimationHandler != null) { + mSpringAnimationHandler.addMovement(e); + } + return super.onTouchEvent(e); + } + /** * Sets the list of apps in this view, used to determine the fastscroll position. */ @@ -85,8 +97,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView { mFastScrollHelper = new AllAppsFastScrollHelper(this, apps); } - public void setElevationController(HeaderElevationController elevationController) { - mElevationController = elevationController; + public AlphabeticalAppsList getApps() { + return mApps; } /** @@ -152,13 +164,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView { */ public void scrollToTop() { // Ensure we reattach the scrollbar if it was previously detached while fast-scrolling - if (mScrollbar.isThumbDetached()) { - mScrollbar.reattachThumbToScroll(); - } + mScrollbar.reattachThumbToScroll(); scrollToPosition(0); - if (mElevationController != null) { - mElevationController.reset(); - } } @Override @@ -403,21 +410,14 @@ public class AllAppsRecyclerView extends BaseRecyclerView { return getPaddingTop() + y - offset; } - @Override - protected int getScrollbarTrackHeight() { - return super.getScrollbarTrackHeight() - - Launcher.getLauncher(getContext()).getDragLayer().getInsets().bottom; - } - /** * Returns the available scroll height: * AvailableScrollHeight = Total height of the all items - last page height */ @Override protected int getAvailableScrollHeight() { - int paddedHeight = getCurrentScrollY(mApps.getAdapterItems().size(), 0); - int totalHeight = paddedHeight + getPaddingBottom(); - return totalHeight - getScrollbarTrackHeight(); + return getCurrentScrollY(mApps.getAdapterItems().size(), 0) + - getHeight() + getPaddingBottom(); } /** diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java index 6587ad78c..517dc947e 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java @@ -20,7 +20,6 @@ import android.graphics.Bitmap; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; -import android.widget.FrameLayout; import android.widget.RelativeLayout; import com.android.launcher3.BubbleTextView; diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 30ed180e7..d79b0d19d 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -22,6 +22,10 @@ import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; +import com.android.launcher3.anim.SpringAnimationHandler; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.graphics.GradientView; +import com.android.launcher3.graphics.ScrimView; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.util.Themes; @@ -38,12 +42,13 @@ import com.android.launcher3.util.TouchController; * closer to top or closer to the page indicator. */ public class AllAppsTransitionController implements TouchController, VerticalPullDetector.Listener, - View.OnLayoutChangeListener { + SearchUiManager.OnScrollRangeChangeListener { private static final String TAG = "AllAppsTrans"; private static final boolean DBG = false; - private final Interpolator mAccelInterpolator = new AccelerateInterpolator(2f); + private final Interpolator mWorkspaceAccelnterpolator = new AccelerateInterpolator(2f); + private final Interpolator mHotseatAccelInterpolator = new AccelerateInterpolator(.5f); private final Interpolator mDecelInterpolator = new DecelerateInterpolator(3f); private final Interpolator mFastOutSlowInInterpolator = new FastOutSlowInInterpolator(); private final VerticalPullDetector.ScrollInterpolator mScrollInterpolator @@ -65,6 +70,8 @@ public class AllAppsTransitionController implements TouchController, VerticalPul private final Launcher mLauncher; private final VerticalPullDetector mDetector; private final ArgbEvaluator mEvaluator; + private final boolean mIsDarkTheme; + private final boolean mIsWorkspaceDarkText; // Animation in this class is controlled by a single variable {@link mProgress}. // Visually, it represents top y coordinate of the all apps container if multiplied with @@ -87,10 +94,15 @@ public class AllAppsTransitionController implements TouchController, VerticalPul private AnimatorSet mCurrentAnimation; private boolean mNoIntercept; + private boolean mTouchEventStartedOnHotseat; // Used in discovery bounce animation to provide the transition without workspace changing. private boolean mIsTranslateWithoutWorkspace = false; private AnimatorSet mDiscoBounceAnimation; + private GradientView mGradientView; + private ScrimView mScrimView; + + private SpringAnimationHandler mSpringAnimationHandler; public AllAppsTransitionController(Launcher l) { mLauncher = l; @@ -101,12 +113,15 @@ public class AllAppsTransitionController implements TouchController, VerticalPul mEvaluator = new ArgbEvaluator(); mAllAppsBackgroundColor = Themes.getAttrColor(l, android.R.attr.colorPrimary); + mIsDarkTheme = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark); + mIsWorkspaceDarkText = Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText); } @Override public boolean onControllerInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { mNoIntercept = false; + mTouchEventStartedOnHotseat = mLauncher.getDragLayer().isEventOverHotseat(ev); if (!mLauncher.isAllAppsVisible() && mLauncher.getWorkspace().workspaceInModalState()) { mNoIntercept = true; } else if (mLauncher.isAllAppsVisible() && @@ -153,6 +168,9 @@ public class AllAppsTransitionController implements TouchController, VerticalPul @Override public boolean onControllerTouchEvent(MotionEvent ev) { + if (hasSpringAnimationHandler()) { + mSpringAnimationHandler.addMovement(ev); + } return mDetector.onTouchEvent(ev); } @@ -171,6 +189,9 @@ public class AllAppsTransitionController implements TouchController, VerticalPul mCurrentAnimation = LauncherAnimUtils.createAnimatorSet(); mShiftStart = mAppsView.getTranslationY(); preparePull(start); + if (hasSpringAnimationHandler()) { + mSpringAnimationHandler.skipToEnd(); + } } @Override @@ -193,6 +214,9 @@ public class AllAppsTransitionController implements TouchController, VerticalPul return; // early termination. } + final int containerType = mTouchEventStartedOnHotseat + ? ContainerType.HOTSEAT : ContainerType.WORKSPACE; + if (fling) { if (velocity < 0) { calculateDuration(velocity, mAppsView.getTranslationY()); @@ -201,11 +225,14 @@ public class AllAppsTransitionController implements TouchController, VerticalPul mLauncher.getUserEventDispatcher().logActionOnContainer( Action.Touch.FLING, Action.Direction.UP, - ContainerType.HOTSEAT); + containerType); } mLauncher.showAppsView(true /* animated */, false /* updatePredictedApps */, false /* focusSearchBar */); + if (hasSpringAnimationHandler()) { + mSpringAnimationHandler.animateToFinalPosition(0); + } } else { calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY())); mLauncher.showWorkspace(true); @@ -221,7 +248,7 @@ public class AllAppsTransitionController implements TouchController, VerticalPul mLauncher.getUserEventDispatcher().logActionOnContainer( Action.Touch.SWIPE, Action.Direction.UP, - ContainerType.HOTSEAT); + containerType); } mLauncher.showAppsView(true, /* animated */ false /* updatePredictedApps */, @@ -247,14 +274,17 @@ public class AllAppsTransitionController implements TouchController, VerticalPul if (!mLauncher.isAllAppsVisible()) { mLauncher.tryAndUpdatePredictedApps(); mAppsView.setVisibility(View.VISIBLE); - mAppsView.setRevealDrawableColor(mHotseatBackgroundColor); + if (!FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { + mAppsView.setRevealDrawableColor(mHotseatBackgroundColor); + } } } } private void updateLightStatusBar(float shift) { - // Do not modify status bar on landscape as all apps is not full bleed. - if (mLauncher.getDeviceProfile().isVerticalBarLayout()) { + // Do not modify status bar in dark theme or on landscape as all apps is not full bleed. + if (mIsDarkTheme || mIsWorkspaceDarkText || (!FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS + && mLauncher.getDeviceProfile().isVerticalBarLayout())) { return; } // Use a light status bar (dark icons) if all apps is behind at least half of the status @@ -263,6 +293,22 @@ public class AllAppsTransitionController implements TouchController, VerticalPul mLauncher.activateLightSystemBars(forceLight, true /* statusBar */, true /* navBar */); } + private void updateAllAppsBg(float progress) { + // gradient + if (mGradientView == null) { + mGradientView = (GradientView) mLauncher.findViewById(R.id.gradient_bg); + mGradientView.setVisibility(View.VISIBLE); + } + mGradientView.setProgress(progress); + + // scrim + if (mScrimView == null) { + mScrimView = (ScrimView) mLauncher.findViewById(R.id.scrim_bg); + mScrimView.setVisibility(View.VISIBLE); + } + mScrimView.setProgress(progress); + } + /** * @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace */ @@ -273,31 +319,37 @@ public class AllAppsTransitionController implements TouchController, VerticalPul float workspaceHotseatAlpha = Utilities.boundToRange(progress, 0f, 1f); float alpha = 1 - workspaceHotseatAlpha; - float interpolation = mAccelInterpolator.getInterpolation(workspaceHotseatAlpha); + float workspaceAlpha = mWorkspaceAccelnterpolator.getInterpolation(workspaceHotseatAlpha); + float hotseatAlpha = mHotseatAccelInterpolator.getInterpolation(workspaceHotseatAlpha); int color = (Integer) mEvaluator.evaluate(mDecelInterpolator.getInterpolation(alpha), mHotseatBackgroundColor, mAllAppsBackgroundColor); int bgAlpha = Color.alpha((int) mEvaluator.evaluate(alpha, mHotseatBackgroundColor, mAllAppsBackgroundColor)); - mAppsView.setRevealDrawableColor(ColorUtils.setAlphaComponent(color, bgAlpha)); + if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { + updateAllAppsBg(alpha); + } else { + mAppsView.setRevealDrawableColor(ColorUtils.setAlphaComponent(color, bgAlpha)); + } + mAppsView.getContentView().setAlpha(alpha); mAppsView.setTranslationY(shiftCurrent); if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) { mWorkspace.setHotseatTranslationAndAlpha(Workspace.Direction.Y, -mShiftRange + shiftCurrent, - interpolation); + hotseatAlpha); } else { mWorkspace.setHotseatTranslationAndAlpha(Workspace.Direction.Y, PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent), - interpolation); + hotseatAlpha); } if (mIsTranslateWithoutWorkspace) { return; } mWorkspace.setWorkspaceYTranslationAndAlpha( - PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent), interpolation); + PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent), workspaceAlpha); if (!mDetector.isDraggingState()) { mContainerVelocity = mDetector.computeVelocity(shiftCurrent - shiftPrevious, @@ -451,6 +503,9 @@ public class AllAppsTransitionController implements TouchController, VerticalPul public void finishPullUp() { mHotseat.setVisibility(View.INVISIBLE); + if (hasSpringAnimationHandler()) { + mSpringAnimationHandler.reset(); + } setProgress(0f); } @@ -459,6 +514,9 @@ public class AllAppsTransitionController implements TouchController, VerticalPul mHotseat.setBackgroundTransparent(false /* transparent */); mHotseat.setVisibility(View.VISIBLE); mAppsView.reset(); + if (hasSpringAnimationHandler()) { + mSpringAnimationHandler.reset(); + } setProgress(1f); } @@ -486,21 +544,20 @@ public class AllAppsTransitionController implements TouchController, VerticalPul mAppsView = appsView; mHotseat = hotseat; mWorkspace = workspace; - mHotseat.addOnLayoutChangeListener(this); mHotseat.bringToFront(); mCaretController = new AllAppsCaretController( mWorkspace.getPageIndicator().getCaretDrawable(), mLauncher); + mAppsView.getSearchUiManager().addOnScrollRangeChangeListener(this); + mSpringAnimationHandler = mAppsView.getSpringAnimationHandler(); + } + + private boolean hasSpringAnimationHandler() { + return FeatureFlags.LAUNCHER3_PHYSICS && mSpringAnimationHandler != null; } @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) { - mShiftRange = top; - } else { - mShiftRange = bottom; - } + public void onScrollRangeChanged(int scrollRange) { + mShiftRange = scrollRange; setProgress(mProgress); } - } diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index f5cf7effb..b84c6276a 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -24,7 +24,7 @@ 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; @@ -195,6 +195,8 @@ public class AlphabeticalAppsList { private int mNumPredictedAppsPerRow; private int mNumAppRowsInAdapter; + private boolean mHasSearchDivider = true; + public AlphabeticalAppsList(Context context) { mLauncher = Launcher.getLauncher(context); mIndexer = new AlphabeticIndexCompat(context); @@ -226,6 +228,13 @@ public class AlphabeticalAppsList { } /** + * Returns the predicted apps. + */ + public List<AppInfo> getPredictedApps() { + return mPredictedApps; + } + + /** * Returns fast scroller sections of all the current filtered applications. */ public List<FastScrollSectionInfo> getFastScrollerSections() { @@ -343,6 +352,10 @@ public class AlphabeticalAppsList { onAppsUpdated(); } + public void disableSearchDivider() { + mHasSearchDivider = false; + } + /** * Updates internals when the set of apps are updated. */ @@ -429,8 +442,10 @@ public class AlphabeticalAppsList { } } - // Add the search divider - mAdapterItems.add(AdapterItem.asSearchDivider(position++)); + if (mHasSearchDivider) { + // Add the search divider + mAdapterItems.add(AdapterItem.asSearchDivider(position++)); + } // Process the predicted app components mPredictedApps.clear(); @@ -440,7 +455,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); } } diff --git a/src/com/android/launcher3/allapps/DefaultAppSearchController.java b/src/com/android/launcher3/allapps/DefaultAppSearchController.java deleted file mode 100644 index 57747e367..000000000 --- a/src/com/android/launcher3/allapps/DefaultAppSearchController.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.allapps; - -/** - * The default search controller. - */ -public class DefaultAppSearchController extends AllAppsSearchBarController { - - public DefaultAppSearchAlgorithm onInitializeSearch() { - return new DefaultAppSearchAlgorithm(mApps.getApps()); - } -} diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java new file mode 100644 index 000000000..0d013c73f --- /dev/null +++ b/src/com/android/launcher3/allapps/SearchUiManager.java @@ -0,0 +1,68 @@ +/* + * 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.allapps; + +import android.view.KeyEvent; + +/** + * Interface for controlling the Apps search UI. + */ +public interface SearchUiManager { + + /** + * Initializes the search manager. + */ + void initialize(AlphabeticalAppsList appsList, AllAppsRecyclerView recyclerView); + + /** + * Notifies the search manager that the apps-list has changed and the search UI should be + * updated accordingly. + */ + void refreshSearchResult(); + + /** + * Notifies the search manager to close any active search session. + */ + void reset(); + + /** + * Called before dispatching a key event, in case the search manager wants to initialize + * some UI beforehand. + */ + void preDispatchKeyEvent(KeyEvent keyEvent); + + /** + * Returns true if the IME should be brought back. + * TODO: Remove when removing support for opening all-apps in search mode. + */ + boolean shouldRestoreImeState(); + + /** + * Starts the search UI + * TODO: Remove when removing support for opening all-apps in search mode. + */ + void startAppsSearch(); + + void addOnScrollRangeChangeListener(OnScrollRangeChangeListener listener); + + /** + * Callback for listening to changes in the vertical scroll range when opening all-apps. + */ + interface OnScrollRangeChangeListener { + + void onScrollRangeChanged(int scrollRange); + } +} diff --git a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java index c7ba3abc6..547d9e185 100644 --- a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java +++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java @@ -13,12 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.launcher3.allapps; +package com.android.launcher3.allapps.search; 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; @@ -34,16 +31,18 @@ import android.widget.TextView.OnEditorActionListener; import com.android.launcher3.ExtendedEditText; import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; +import com.android.launcher3.allapps.AlphabeticalAppsList; import com.android.launcher3.discovery.AppDiscoveryItem; import com.android.launcher3.discovery.AppDiscoveryUpdateState; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.PackageManagerHelper; import java.util.ArrayList; /** * An interface to a search box that AllApps can command. */ -public abstract class AllAppsSearchBarController +public class AllAppsSearchBarController implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener { protected Launcher mLauncher; @@ -88,9 +87,11 @@ public abstract class AllAppsSearchBarController } /** - * To be implemented by subclasses. This method will get called when the controller is set. + * This method will get called when the controller is set. */ - protected abstract DefaultAppSearchAlgorithm onInitializeSearch(); + public DefaultAppSearchAlgorithm onInitializeSearch() { + return new DefaultAppSearchAlgorithm(mApps.getApps()); + } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -114,7 +115,7 @@ public abstract class AllAppsSearchBarController } } - protected void refreshSearchResult() { + public void refreshSearchResult() { if (TextUtils.isEmpty(mQuery)) { return; } @@ -135,7 +136,8 @@ public abstract class AllAppsSearchBarController if (query.isEmpty()) { return false; } - return mLauncher.startActivitySafely(v, createMarketSearchIntent(query), null); + return mLauncher.startActivitySafely(v, + PackageManagerHelper.getMarketSearchIntent(mLauncher, query), null); } @Override @@ -186,29 +188,11 @@ public abstract class AllAppsSearchBarController } /** - * Creates a new market search intent. - */ - public Intent createMarketSearchIntent(String query) { - Uri marketSearchUri = Uri.parse("market://search") - .buildUpon() - .appendQueryParameter("c", "apps") - .appendQueryParameter("q", query) - .build(); - return new Intent(Intent.ACTION_VIEW).setData(marketSearchUri); - } - - /** * Callback for getting search results. */ public interface Callbacks { /** - * Called when the bounds of the search bar has changed. - */ - @Deprecated - void onBoundsChanged(Rect newBounds); - - /** * Called when the search is complete. * * @param apps sorted list of matching components or null if in case of failure. @@ -220,7 +204,6 @@ public abstract class AllAppsSearchBarController */ void clearSearchResult(); - /** * Called when the app discovery is providing an update of search, which can either be * START for starting a new discovery, diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java new file mode 100644 index 000000000..126a02c73 --- /dev/null +++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java @@ -0,0 +1,220 @@ +/* + * 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.allapps.search; + +import android.content.Context; +import android.graphics.Rect; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.Selection; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.text.method.TextKeyListener; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; +import android.widget.FrameLayout; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.ExtendedEditText; +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.allapps.AllAppsGridAdapter; +import com.android.launcher3.allapps.AllAppsRecyclerView; +import com.android.launcher3.allapps.AlphabeticalAppsList; +import com.android.launcher3.allapps.SearchUiManager; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.discovery.AppDiscoveryItem; +import com.android.launcher3.discovery.AppDiscoveryUpdateState; +import com.android.launcher3.graphics.TintedDrawableSpan; +import com.android.launcher3.util.ComponentKey; + +import java.util.ArrayList; + +/** + * Layout to contain the All-apps search UI. + */ +public class AppsSearchContainerLayout extends FrameLayout + implements SearchUiManager, AllAppsSearchBarController.Callbacks { + + private final Launcher mLauncher; + private final int mMinHeight; + private final int mSearchBoxHeight; + private final AllAppsSearchBarController mSearchBarController; + private final SpannableStringBuilder mSearchQueryBuilder; + private final HeaderElevationController mElevationController; + + private ExtendedEditText mSearchInput; + private AlphabeticalAppsList mApps; + private AllAppsRecyclerView mAppsRecyclerView; + private AllAppsGridAdapter mAdapter; + + public AppsSearchContainerLayout(Context context) { + this(context, null); + } + + public AppsSearchContainerLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + mLauncher = Launcher.getLauncher(context); + mMinHeight = getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_height); + mSearchBoxHeight = getResources() + .getDimensionPixelSize(R.dimen.all_apps_search_bar_field_height); + mSearchBarController = new AllAppsSearchBarController(); + mElevationController = new HeaderElevationController(this); + + mSearchQueryBuilder = new SpannableStringBuilder(); + Selection.setSelection(mSearchQueryBuilder, 0); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mSearchInput = findViewById(R.id.search_box_input); + + // Update the hint to contain the icon. + // Prefix the original hint with two spaces. The first space gets replaced by the icon + // using span. The second space is used for a singe space character between the hint + // and the icon. + SpannableString spanned = new SpannableString(" " + mSearchInput.getHint()); + spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search), + 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + mSearchInput.setHint(spanned); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && + !mLauncher.getDeviceProfile().isVerticalBarLayout()) { + getLayoutParams().height = mLauncher.getDragLayer().getInsets().top + mMinHeight; + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + + @Override + public void initialize( + AlphabeticalAppsList appsList, AllAppsRecyclerView recyclerView) { + mApps = appsList; + mAppsRecyclerView = recyclerView; + mAppsRecyclerView.addOnScrollListener(mElevationController); + mAdapter = (AllAppsGridAdapter) mAppsRecyclerView.getAdapter(); + + mSearchBarController.initialize(appsList, mSearchInput, mLauncher, this); + } + + @Override + public void refreshSearchResult() { + mSearchBarController.refreshSearchResult(); + } + + @Override + public void reset() { + mElevationController.reset(); + mSearchBarController.reset(); + } + + @Override + public void preDispatchKeyEvent(KeyEvent event) { + // Determine if the key event was actual text, if so, focus the search bar and then dispatch + // the key normally so that it can process this key event + if (!mSearchBarController.isSearchFieldFocused() && + event.getAction() == KeyEvent.ACTION_DOWN) { + final int unicodeChar = event.getUnicodeChar(); + final boolean isKeyNotWhitespace = unicodeChar > 0 && + !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar); + if (isKeyNotWhitespace) { + boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder, + event.getKeyCode(), event); + if (gotKey && mSearchQueryBuilder.length() > 0) { + mSearchBarController.focusSearchField(); + } + } + } + } + + @Override + public boolean shouldRestoreImeState() { + return !TextUtils.isEmpty(mSearchInput.getText()); + } + + @Override + public void startAppsSearch() { + if (mApps != null) { + mSearchBarController.focusSearchField(); + } + } + + @Override + public void onSearchResult(String query, ArrayList<ComponentKey> apps) { + if (apps != null) { + mApps.setOrderedFilter(apps); + notifyResultChanged(); + mAdapter.setLastSearchQuery(query); + } + } + + @Override + public void clearSearchResult() { + if (mApps.setOrderedFilter(null)) { + notifyResultChanged(); + } + + // Clear the search query + mSearchQueryBuilder.clear(); + mSearchQueryBuilder.clearSpans(); + Selection.setSelection(mSearchQueryBuilder, 0); + } + + @Override + public void onAppDiscoverySearchUpdate( + @Nullable AppDiscoveryItem app, @NonNull AppDiscoveryUpdateState state) { + if (!mLauncher.isDestroyed()) { + mApps.onAppDiscoverySearchUpdate(app, state); + notifyResultChanged(); + } + } + + private void notifyResultChanged() { + mElevationController.reset(); + mAppsRecyclerView.onSearchResultsChanged(); + } + + @Override + public void addOnScrollRangeChangeListener(final OnScrollRangeChangeListener listener) { + mLauncher.getHotseat().addOnLayoutChangeListener(new OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) { + DeviceProfile dp = mLauncher.getDeviceProfile(); + if (!dp.isVerticalBarLayout()) { + Rect insets = mLauncher.getDragLayer().getInsets(); + int hotseatBottom = bottom - dp.hotseatBarBottomPaddingPx - insets.bottom; + int searchTopMargin = insets.top + (mMinHeight - mSearchBoxHeight); + listener.onScrollRangeChanged(hotseatBottom - searchTopMargin); + } else { + listener.onScrollRangeChanged(bottom); + } + } + }); + } +} diff --git a/src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java index 06cf9aa71..06097d0e6 100644 --- a/src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java +++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.launcher3.allapps; +package com.android.launcher3.allapps.search; import android.os.Handler; @@ -54,7 +54,7 @@ public class DefaultAppSearchAlgorithm { }); } - protected ArrayList<ComponentKey> getTitleMatchResult(String query) { + public ArrayList<ComponentKey> getTitleMatchResult(String query) { // Do an intersection of the words in the query and each title, and filter out all the // apps that don't match all of the words in the query. final String queryTextLower = query.toLowerCase(); @@ -67,7 +67,7 @@ public class DefaultAppSearchAlgorithm { return result; } - protected boolean matches(AppInfo info, String query) { + public boolean matches(AppInfo info, String query) { int queryLength = query.length(); String title = info.title.toString(); diff --git a/src/com/android/launcher3/allapps/HeaderElevationController.java b/src/com/android/launcher3/allapps/search/HeaderElevationController.java index b167fed33..ab4e88fc8 100644 --- a/src/com/android/launcher3/allapps/HeaderElevationController.java +++ b/src/com/android/launcher3/allapps/search/HeaderElevationController.java @@ -1,4 +1,4 @@ -package com.android.launcher3.allapps; +package com.android.launcher3.allapps.search; import android.content.res.Resources; import android.graphics.Outline; 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/anim/PillHeightRevealOutlineProvider.java b/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java deleted file mode 100644 index be1e2d644..000000000 --- a/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.anim; - -import android.graphics.Rect; - -import com.android.launcher3.util.PillRevealOutlineProvider; - -/** - * 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 PillHeightRevealOutlineProvider extends PillRevealOutlineProvider { - - private final int mNewHeight; - - public PillHeightRevealOutlineProvider(Rect pillRect, float radius, int newHeight) { - super(0, 0, pillRect, radius); - mOutline.set(pillRect); - mNewHeight = newHeight; - } - - @Override - public void setProgress(float progress) { - mOutline.top = 0; - int heightDifference = mPillRect.height() - mNewHeight; - mOutline.bottom = (int) (mPillRect.bottom - heightDifference * (1 - progress)); - } -} 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..d01b26c8f --- /dev/null +++ b/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java @@ -0,0 +1,77 @@ +/* + * 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; + +import com.android.launcher3.popup.PopupContainerWithArrow; + +import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS; +import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS; + +/** + * 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; + + private final @PopupContainerWithArrow.RoundedCornerFlags int mRoundedCorners; + + public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect, + Rect endRect) { + this(startRadius, endRadius, startRect, endRect, + ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS); + } + + public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect, + Rect endRect, int roundedCorners) { + mStartRadius = startRadius; + mEndRadius = endRadius; + mStartRect = startRect; + mEndRect = endRect; + mRoundedCorners = roundedCorners; + } + + @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); + if ((mRoundedCorners & ROUNDED_TOP_CORNERS) == 0) { + mOutline.top -= mOutlineRadius; + } + mOutline.right = (int) ((1 - progress) * mStartRect.right + progress * mEndRect.right); + mOutline.bottom = (int) ((1 - progress) * mStartRect.bottom + progress * mEndRect.bottom); + if ((mRoundedCorners & ROUNDED_BOTTOM_CORNERS) == 0) { + mOutline.bottom += mOutlineRadius; + } + } +} diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java new file mode 100644 index 000000000..038f82682 --- /dev/null +++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java @@ -0,0 +1,214 @@ +/* + * 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.support.animation.FloatPropertyCompat; +import android.support.animation.SpringAnimation; +import android.support.animation.SpringForce; +import android.support.annotation.IntDef; +import android.util.Log; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; + +import com.android.launcher3.R; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + +/** + * Handler class that manages springs for a set of views that should all move based on the same + * {@link MotionEvent}s. + * + * Supports setting either X or Y velocity on the list of springs added to this handler. + */ +public class SpringAnimationHandler<T> { + + private static final String TAG = "SpringAnimationHandler"; + private static final boolean DEBUG = false; + + private static final float VELOCITY_DAMPING_FACTOR = 0.175f; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({Y_DIRECTION, X_DIRECTION}) + public @interface Direction {} + public static final int Y_DIRECTION = 0; + public static final int X_DIRECTION = 1; + private int mVelocityDirection; + + private VelocityTracker mVelocityTracker; + private float mCurrentVelocity = 0; + private boolean mShouldComputeVelocity = false; + + private AnimationFactory<T> mAnimationFactory; + + private ArrayList<SpringAnimation> mAnimations = new ArrayList<>(); + + /** + * @param direction Either {@link #X_DIRECTION} or {@link #Y_DIRECTION}. + * Determines which direction we use to calculate and set the velocity. + * @param factory The AnimationFactory is responsible for initializing and updating the + * SpringAnimations added to this class. + */ + public SpringAnimationHandler(@Direction int direction, AnimationFactory<T> factory) { + mVelocityDirection = direction; + mAnimationFactory = factory; + } + + /** + * Adds a new or recycled animation to the list of springs handled by this class. + * + * @param view The view the spring is attached to. + * @param object Used to initialize and update the spring. + */ + public void add(View view, T object) { + SpringAnimation spring = (SpringAnimation) view.getTag(R.id.spring_animation_tag); + if (spring == null) { + spring = mAnimationFactory.initialize(object); + view.setTag(R.id.spring_animation_tag, spring); + } + mAnimationFactory.update(spring, object); + spring.setStartVelocity(mCurrentVelocity); + mAnimations.add(spring); + } + + /** + * Stops and removes the spring attached to {@param view}. + */ + public void remove(View view) { + SpringAnimation animation = (SpringAnimation) view.getTag(R.id.spring_animation_tag); + if (animation.canSkipToEnd()) { + animation.skipToEnd(); + } + mAnimations.remove(animation); + } + + public void addMovement(MotionEvent event) { + int action = event.getActionMasked(); + if (DEBUG) Log.d(TAG, "addMovement#action=" + action); + switch (action) { + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_DOWN: + reset(); + break; + } + + getVelocityTracker().addMovement(event); + mShouldComputeVelocity = true; + } + + public void animateToFinalPosition(float position) { + if (DEBUG) Log.d(TAG, "animateToFinalPosition#computeVelocity=" + mShouldComputeVelocity); + + if (mShouldComputeVelocity) { + computeVelocity(); + setStartVelocity(mCurrentVelocity); + } + + int size = mAnimations.size(); + for (int i = 0; i < size; ++i) { + mAnimations.get(i).animateToFinalPosition(position); + } + + reset(); + } + + public boolean isRunning() { + // All the animations run at the same time so we can just check the first one. + return !mAnimations.isEmpty() && mAnimations.get(0).isRunning(); + } + + public void skipToEnd() { + if (DEBUG) Log.d(TAG, "setStartVelocity#skipToEnd"); + if (DEBUG) Log.v(TAG, "setStartVelocity#skipToEnd", new Exception()); + + int size = mAnimations.size(); + for (int i = 0; i < size; ++i) { + if (mAnimations.get(i).canSkipToEnd()) { + mAnimations.get(i).skipToEnd(); + } + } + } + + public void reset() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + mCurrentVelocity = 0; + } + + private void setStartVelocity(float velocity) { + int size = mAnimations.size(); + for (int i = 0; i < size; ++i) { + mAnimations.get(i).setStartVelocity(velocity); + } + } + + private void computeVelocity() { + getVelocityTracker().computeCurrentVelocity(1000 /* millis */); + + mCurrentVelocity = isVerticalDirection() + ? getVelocityTracker().getYVelocity() + : getVelocityTracker().getXVelocity(); + mCurrentVelocity *= VELOCITY_DAMPING_FACTOR; + mShouldComputeVelocity = false; + + if (DEBUG) Log.d(TAG, "computeVelocity=" + mCurrentVelocity); + } + + private boolean isVerticalDirection() { + return mVelocityDirection == Y_DIRECTION; + } + + private VelocityTracker getVelocityTracker() { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + return mVelocityTracker; + } + + /** + * This interface is used to initialize and update the SpringAnimations added to the + * {@link SpringAnimationHandler}. + * + * @param <T> The object that each SpringAnimation is attached to. + */ + public interface AnimationFactory<T> { + + /** + * Initializes a new Spring for {@param object}. + */ + SpringAnimation initialize(T object); + + /** + * Updates the value of {@param spring} based on {@param object}. + */ + void update(SpringAnimation spring, T object); + } + + /** + * Helper method to create a new SpringAnimation for {@param view}. + */ + public static SpringAnimation forView(View view, FloatPropertyCompat property, float finalPos) { + SpringAnimation spring = new SpringAnimation(view, property, finalPos); + spring.setStartValue(1f); + spring.setSpring(new SpringForce(finalPos)); + return spring; + } + +} diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java index e997a9993..26f4ae721 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompat.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java @@ -26,13 +26,8 @@ import android.os.Bundle; import android.os.UserHandle; import android.support.annotation.Nullable; -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel; -import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; -import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.shortcuts.ShortcutInfoCompat; -import com.android.launcher3.util.LooperExecuter; import com.android.launcher3.util.PackageUserKey; import java.util.List; @@ -88,62 +83,6 @@ public abstract class LauncherAppsCompat { public abstract List<ShortcutConfigActivityInfo> getCustomShortcutActivityList( @Nullable PackageUserKey packageUser); - /** - * request.accept() will initiate the following flow: - * -> go-to-system-process for actual processing (a) - * -> callback-to-launcher on UI thread (b) - * -> post callback on the worker thread (c) - * -> Update model and unpin (in system) any shortcut not in out model. (d) - * - * Note that (b) will take at-least one frame as it involves posting callback from binder - * thread to UI thread. - * If (d) happens before we add this shortcut to our model, we will end up unpinning - * the shortcut in the system. - * Here its the caller's responsibility to add the newly created ShortcutInfo immediately - * to the model (which may involves a single post-to-worker-thread). That will guarantee - * that (d) happens after model is updated. - */ - @Nullable - public static ShortcutInfo createShortcutInfoFromPinItemRequest( - Context context, final PinItemRequestCompat request, final long acceptDelay) { - if (request != null && - request.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT && - request.isValid()) { - - if (acceptDelay <= 0) { - if (!request.accept()) { - return null; - } - } else { - // Block the worker thread until the accept() is called. - new LooperExecuter(LauncherModel.getWorkerLooper()).execute(new Runnable() { - @Override - public void run() { - try { - Thread.sleep(acceptDelay); - } catch (InterruptedException e) { - // Ignore - } - if (request.isValid()) { - request.accept(); - } - } - }); - } - - ShortcutInfoCompat compat = new ShortcutInfoCompat(request.getShortcutInfo()); - ShortcutInfo info = new ShortcutInfo(compat, context); - // Apply the unbadged icon and fetch the actual icon asynchronously. - info.iconBitmap = LauncherIcons - .createShortcutIcon(compat, context, false /* badged */); - LauncherAppState.getInstance(context).getModel() - .updateAndBindShortcutInfo(info, compat); - return info; - } else { - return null; - } - } - public void showAppDetailsForProfile(ComponentName component, UserHandle user) { showAppDetailsForProfile(component, user, null, null); } diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java index d145539c7..3214b4672 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java @@ -18,20 +18,27 @@ package com.android.launcher3.compat; import android.annotation.TargetApi; 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.LauncherApps.PinItemRequest; import android.content.pm.PackageManager; import android.os.Build; +import android.os.Parcelable; import android.os.Process; import android.os.UserHandle; import android.support.annotation.Nullable; -import android.util.Log; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.ShortcutInfo; import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO; +import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.shortcuts.ShortcutInfoCompat; +import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.PackageUserKey; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -45,11 +52,6 @@ public class LauncherAppsCompatVO extends LauncherAppsCompatVL { @Override public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) { try { - // TODO: Temporary workaround until the API signature is updated - if (false) { - throw new PackageManager.NameNotFoundException(); - } - ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user); return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled ? null : info; @@ -64,34 +66,89 @@ public class LauncherAppsCompatVO extends LauncherAppsCompatVL { List<ShortcutConfigActivityInfo> result = new ArrayList<>(); UserHandle myUser = Process.myUserHandle(); - try { - Method m = LauncherApps.class.getDeclaredMethod("getShortcutConfigActivityList", - String.class, UserHandle.class); - final List<UserHandle> users; - final String packageName; - if (packageUser == null) { - users = UserManagerCompat.getInstance(mContext).getUserProfiles(); - packageName = null; - } else { - users = new ArrayList<>(1); - users.add(packageUser.mUser); - packageName = packageUser.mPackageName; - } - for (UserHandle user : users) { - boolean ignoreTargetSdk = myUser.equals(user); - List<LauncherActivityInfo> activities = - (List<LauncherActivityInfo>) m.invoke(mLauncherApps, packageName, user); - for (LauncherActivityInfo activityInfo : activities) { - if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion >= - Build.VERSION_CODES.O) { - result.add(new ShortcutConfigActivityInfoVO(activityInfo)); - } + final List<UserHandle> users; + final String packageName; + if (packageUser == null) { + users = UserManagerCompat.getInstance(mContext).getUserProfiles(); + packageName = null; + } else { + users = new ArrayList<>(1); + users.add(packageUser.mUser); + packageName = packageUser.mPackageName; + } + for (UserHandle user : users) { + boolean ignoreTargetSdk = myUser.equals(user); + List<LauncherActivityInfo> activities = + mLauncherApps.getShortcutConfigActivityList(packageName, user); + for (LauncherActivityInfo activityInfo : activities) { + if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion >= + Build.VERSION_CODES.O) { + result.add(new ShortcutConfigActivityInfoVO(activityInfo)); } } - } catch (Exception e) { - Log.e("LauncherAppsCompatVO", "Error calling new API", e); } return result; } + + /** + * request.accept() will initiate the following flow: + * -> go-to-system-process for actual processing (a) + * -> callback-to-launcher on UI thread (b) + * -> post callback on the worker thread (c) + * -> Update model and unpin (in system) any shortcut not in out model. (d) + * + * Note that (b) will take at-least one frame as it involves posting callback from binder + * thread to UI thread. + * If (d) happens before we add this shortcut to our model, we will end up unpinning + * the shortcut in the system. + * Here its the caller's responsibility to add the newly created ShortcutInfo immediately + * to the model (which may involves a single post-to-worker-thread). That will guarantee + * that (d) happens after model is updated. + */ + @Nullable + public static ShortcutInfo createShortcutInfoFromPinItemRequest( + Context context, final PinItemRequest request, final long acceptDelay) { + if (request != null && + request.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT && + request.isValid()) { + + if (acceptDelay <= 0) { + if (!request.accept()) { + return null; + } + } else { + // Block the worker thread until the accept() is called. + new LooperExecutor(LauncherModel.getWorkerLooper()).execute(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(acceptDelay); + } catch (InterruptedException e) { + // Ignore + } + if (request.isValid()) { + request.accept(); + } + } + }); + } + + ShortcutInfoCompat compat = new ShortcutInfoCompat(request.getShortcutInfo()); + ShortcutInfo info = new ShortcutInfo(compat, context); + // Apply the unbadged icon and fetch the actual icon asynchronously. + info.iconBitmap = LauncherIcons + .createShortcutIcon(compat, context, false /* badged */); + LauncherAppState.getInstance(context).getModel() + .updateAndBindShortcutInfo(info, compat); + return info; + } else { + return null; + } + } + + public static PinItemRequest getPinItemRequest(Intent intent) { + Parcelable extra = intent.getParcelableExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST); + return extra instanceof PinItemRequest ? (PinItemRequest) extra : null; + } } diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java index c7fe0cec8..112cca540 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompat.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java @@ -16,9 +16,13 @@ package com.android.launcher3.compat; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageInstaller; +import android.support.annotation.NonNull; import java.util.HashMap; +import java.util.List; public abstract class PackageInstallerCompat { @@ -46,19 +50,34 @@ public abstract class PackageInstallerCompat { public abstract void onStop(); public static final class PackageInstallInfo { + public final ComponentName componentName; public final String packageName; + public final int state; + public final int progress; - public int state; - public int progress; - - public PackageInstallInfo(String packageName) { - this.packageName = packageName; + private PackageInstallInfo(@NonNull PackageInstaller.SessionInfo info) { + this.state = STATUS_INSTALLING; + this.packageName = info.getAppPackageName(); + this.componentName = new ComponentName(packageName, ""); + this.progress = (int) (info.getProgress() * 100f); } public PackageInstallInfo(String packageName, int state, int progress) { - this.packageName = packageName; this.state = state; + this.packageName = packageName; + this.componentName = new ComponentName(packageName, ""); this.progress = progress; } + + public static PackageInstallInfo fromInstallingState(PackageInstaller.SessionInfo info) { + return new PackageInstallInfo(info); + } + + public static PackageInstallInfo fromState(int state, String packageName) { + return new PackageInstallInfo(packageName, state, 0 /* progress */); + } + } + + public abstract List<PackageInstaller.SessionInfo> getAllVerifiedSessions(); } diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java index b87582f55..1ffd3da01 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java @@ -17,34 +17,44 @@ package com.android.launcher3.compat; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionCallback; import android.content.pm.PackageInstaller.SessionInfo; import android.os.Handler; import android.os.Process; import android.os.UserHandle; +import android.text.TextUtils; import android.util.SparseArray; import com.android.launcher3.IconCache; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.Thunk; +import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; +import java.util.List; public class PackageInstallerCompatVL extends PackageInstallerCompat { + private static final boolean DEBUG = false; + @Thunk final SparseArray<String> mActiveSessions = new SparseArray<>(); @Thunk final PackageInstaller mInstaller; private final IconCache mCache; private final Handler mWorker; + private final Context mAppContext; + private final HashMap<String,Boolean> mSessionVerifiedMap = new HashMap<>(); PackageInstallerCompatVL(Context context) { + mAppContext = context.getApplicationContext(); mInstaller = context.getPackageManager().getPackageInstaller(); mCache = LauncherAppState.getInstance(context).getIconCache(); mWorker = new Handler(LauncherModel.getWorkerLooper()); - mInstaller.registerSessionCallback(mCallback, mWorker); } @@ -52,7 +62,7 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { public HashMap<String, Integer> updateAndGetActiveSessionCache() { HashMap<String, Integer> activePackages = new HashMap<>(); UserHandle user = Process.myUserHandle(); - for (SessionInfo info : mInstaller.getAllSessions()) { + for (SessionInfo info : getAllVerifiedSessions()) { addSessionInfoToCache(info, user); if (info.getAppPackageName() != null) { activePackages.put(info.getAppPackageName(), (int) (info.getProgress() * 100)); @@ -86,7 +96,14 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { @Override public void onCreated(int sessionId) { - pushSessionDisplayToLauncher(sessionId); + SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId); + if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS && sessionInfo != null) { + LauncherAppState app = LauncherAppState.getInstanceNoCreate(); + if (app != null) { + app.getModel().onInstallSessionCreated( + PackageInstallInfo.fromInstallingState(sessionInfo)); + } + } } @Override @@ -97,18 +114,17 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { mActiveSessions.remove(sessionId); if (packageName != null) { - sendUpdate(new PackageInstallInfo(packageName, - success ? STATUS_INSTALLED : STATUS_FAILED, 0)); + sendUpdate(PackageInstallInfo.fromState( + success ? STATUS_INSTALLED : STATUS_FAILED, + packageName)); } } @Override public void onProgressChanged(int sessionId, float progress) { - SessionInfo session = mInstaller.getSessionInfo(sessionId); + SessionInfo session = verify(mInstaller.getSessionInfo(sessionId)); if (session != null && session.getAppPackageName() != null) { - sendUpdate(new PackageInstallInfo(session.getAppPackageName(), - STATUS_INSTALLING, - (int) (session.getProgress() * 100))); + sendUpdate(PackageInstallInfo.fromInstallingState(session)); } } @@ -120,16 +136,48 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { pushSessionDisplayToLauncher(sessionId); } - private void pushSessionDisplayToLauncher(int sessionId) { - SessionInfo session = mInstaller.getSessionInfo(sessionId); + private SessionInfo pushSessionDisplayToLauncher(int sessionId) { + SessionInfo session = verify(mInstaller.getSessionInfo(sessionId)); if (session != null && session.getAppPackageName() != null) { + mActiveSessions.put(sessionId, session.getAppPackageName()); addSessionInfoToCache(session, Process.myUserHandle()); LauncherAppState app = LauncherAppState.getInstanceNoCreate(); - if (app != null) { app.getModel().updateSessionDisplayInfo(session.getAppPackageName()); } + return session; } + return null; } }; + + private PackageInstaller.SessionInfo verify(PackageInstaller.SessionInfo sessionInfo) { + if (sessionInfo == null + || sessionInfo.getInstallerPackageName() == null + || TextUtils.isEmpty(sessionInfo.getAppPackageName())) { + return null; + } + String pkg = sessionInfo.getInstallerPackageName(); + synchronized (mSessionVerifiedMap) { + if (!mSessionVerifiedMap.containsKey(pkg)) { + LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mAppContext); + boolean hasSystemFlag = launcherApps.getApplicationInfo(pkg, + ApplicationInfo.FLAG_SYSTEM, Process.myUserHandle()) != null; + mSessionVerifiedMap.put(pkg, DEBUG || hasSystemFlag); + } + } + return mSessionVerifiedMap.get(pkg) ? sessionInfo : null; + } + + @Override + public List<SessionInfo> getAllVerifiedSessions() { + List<SessionInfo> list = new ArrayList<>(mInstaller.getAllSessions()); + Iterator<SessionInfo> it = list.iterator(); + while (it.hasNext()) { + if (verify(it.next()) == null) { + it.remove(); + } + } + return list; + } } diff --git a/src/com/android/launcher3/compat/PinItemRequestCompat.java b/src/com/android/launcher3/compat/PinItemRequestCompat.java deleted file mode 100644 index 1308cba97..000000000 --- a/src/com/android/launcher3/compat/PinItemRequestCompat.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.compat; - -import android.appwidget.AppWidgetProviderInfo; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ShortcutInfo; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.launcher3.Utilities; - -/** - * A wrapper around platform implementation of PinItemRequestCompat until the - * updated SDK is available. - */ -public class PinItemRequestCompat implements Parcelable { - - public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST"; - - public static final int REQUEST_TYPE_SHORTCUT = 1; - public static final int REQUEST_TYPE_APPWIDGET = 2; - - private final Parcelable mObject; - - private PinItemRequestCompat(Parcelable object) { - mObject = object; - } - - public int getRequestType() { - return (Integer) invokeMethod("getRequestType"); - } - - public ShortcutInfo getShortcutInfo() { - return (ShortcutInfo) invokeMethod("getShortcutInfo"); - } - - public AppWidgetProviderInfo getAppWidgetProviderInfo(Context context) { - try { - return (AppWidgetProviderInfo) mObject.getClass() - .getDeclaredMethod("getAppWidgetProviderInfo", Context.class) - .invoke(mObject, context); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public boolean isValid() { - return (Boolean) invokeMethod("isValid"); - } - - public boolean accept() { - return (Boolean) invokeMethod("accept"); - } - - public boolean accept(Bundle options) { - try { - return (Boolean) mObject.getClass().getDeclaredMethod("accept", Bundle.class) - .invoke(mObject, options); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public Bundle getExtras() { - try { - return (Bundle) mObject.getClass().getDeclaredMethod("getExtras").invoke(mObject); - } catch (Exception e) { - return null; - } - } - - private Object invokeMethod(String methodName) { - try { - return mObject.getClass().getDeclaredMethod(methodName).invoke(mObject); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int i) { - parcel.writeParcelable(mObject, i); - } - - public static final Parcelable.Creator<PinItemRequestCompat> CREATOR = - new Parcelable.Creator<PinItemRequestCompat>() { - public PinItemRequestCompat createFromParcel(Parcel source) { - Parcelable object = source.readParcelable(null); - return new PinItemRequestCompat(object); - } - - public PinItemRequestCompat[] newArray(int size) { - return new PinItemRequestCompat[size]; - } - }; - - public static PinItemRequestCompat getPinItemRequest(Intent intent) { - if (!Utilities.isAtLeastO()) { - return null; - } - Parcelable extra = intent.getParcelableExtra(EXTRA_PIN_ITEM_REQUEST); - return extra == null ? null : new PinItemRequestCompat(extra); - } -} diff --git a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java index 4a55e8ca0..6bdc62743 100644 --- a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java +++ b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java @@ -37,8 +37,6 @@ import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.ShortcutInfo; -import java.lang.reflect.Method; - /** * Wrapper class for representing a shortcut configure activity. */ @@ -151,15 +149,13 @@ public abstract class ShortcutConfigActivityInfo { if (getUser().equals(Process.myUserHandle())) { return super.startConfigActivity(activity, requestCode); } + IntentSender is = activity.getSystemService(LauncherApps.class) + .getShortcutConfigActivityIntent(mInfo); try { - Method m = LauncherApps.class.getDeclaredMethod( - "getShortcutConfigActivityIntent", LauncherActivityInfo.class); - IntentSender is = (IntentSender) m.invoke( - activity.getSystemService(LauncherApps.class), mInfo); activity.startIntentSenderForResult(is, requestCode, null, 0, 0, 0); return true; - } catch (Exception e) { - Log.e(TAG, "Error calling new API", e); + } catch (IntentSender.SendIntentException e) { + Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); return false; } } diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java index 45525f521..c7f88f63d 100644 --- a/src/com/android/launcher3/compat/UserManagerCompatVL.java +++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java @@ -22,8 +22,8 @@ import android.content.pm.PackageManager; import android.os.UserHandle; import android.os.UserManager; -import com.android.launcher3.Utilities; import com.android.launcher3.util.LongArrayMap; +import com.android.launcher3.util.ManagedProfileHeuristic; import java.util.ArrayList; import java.util.Collections; @@ -122,7 +122,7 @@ public class UserManagerCompatVL extends UserManagerCompat { @Override public long getUserCreationTime(UserHandle user) { - SharedPreferences prefs = Utilities.getPrefs(mContext); + SharedPreferences prefs = ManagedProfileHeuristic.prefs(mContext); String key = USER_CREATION_TIME_KEY + getSerialNumberForUser(user); if (!prefs.contains(key)) { prefs.edit().putLong(key, System.currentTimeMillis()).apply(); diff --git a/src/com/android/launcher3/compat/WallpaperColorsCompat.java b/src/com/android/launcher3/compat/WallpaperColorsCompat.java new file mode 100644 index 000000000..fd08f947e --- /dev/null +++ b/src/com/android/launcher3/compat/WallpaperColorsCompat.java @@ -0,0 +1,43 @@ +/* + * 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.compat; + +import android.util.SparseIntArray; + +/** + * A compatibility layer around platform implementation of WallpaperColors + */ +public class WallpaperColorsCompat { + + private final SparseIntArray mColors; + private final boolean mSupportsDarkText; + + public WallpaperColorsCompat(SparseIntArray colors, boolean supportsDarkText) { + mColors = colors; + mSupportsDarkText = supportsDarkText; + } + + /** + * A map of color code to their occurrences. The bigger the int, the more relevant the color. + */ + public SparseIntArray getColors() { + return mColors; + } + + public boolean supportsDarkText() { + return mSupportsDarkText; + } +} diff --git a/src/com/android/launcher3/compat/WallpaperManagerCompat.java b/src/com/android/launcher3/compat/WallpaperManagerCompat.java new file mode 100644 index 000000000..cbcabdf9b --- /dev/null +++ b/src/com/android/launcher3/compat/WallpaperManagerCompat.java @@ -0,0 +1,61 @@ +/* + * 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.compat; + +import android.content.Context; +import android.support.annotation.Nullable; + +import com.android.launcher3.Utilities; + +public abstract class WallpaperManagerCompat { + + private static final Object sInstanceLock = new Object(); + private static WallpaperManagerCompat sInstance; + + public static WallpaperManagerCompat getInstance(Context context) { + synchronized (sInstanceLock) { + if (sInstance == null) { + context = context.getApplicationContext(); + + if (Utilities.isAtLeastO()) { + try { + sInstance = new WallpaperManagerCompatVOMR1(context); + } catch (Exception e) { + // The wallpaper APIs do not yet exist + } + } + if (sInstance == null) { + sInstance = new WallpaperManagerCompatVL(context); + } + } + return sInstance; + } + } + + + public abstract @Nullable WallpaperColorsCompat getWallpaperColors(int which); + + public abstract void addOnColorsChangedListener(OnColorsChangedListenerCompat listener); + + /** + * Interface definition for a callback to be invoked when colors change on a wallpaper. + */ + public interface OnColorsChangedListenerCompat { + + void onColorsChanged(WallpaperColorsCompat colors, int which); + } +} diff --git a/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java b/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java new file mode 100644 index 000000000..d61b5640a --- /dev/null +++ b/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java @@ -0,0 +1,232 @@ +/* + * 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.compat; + +import static android.app.WallpaperManager.FLAG_SYSTEM; + +import static com.android.launcher3.Utilities.getDevicePrefs; + +import android.app.IntentService; +import android.app.WallpaperInfo; +import android.app.WallpaperManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapRegionDecoder; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.ParcelFileDescriptor; +import android.os.ResultReceiver; +import android.support.annotation.Nullable; +import android.support.v7.graphics.Palette; +import android.util.Log; +import android.util.Pair; +import android.util.SparseIntArray; + +import com.android.launcher3.Utilities; + +import java.io.IOException; +import java.util.ArrayList; + +public class WallpaperManagerCompatVL extends WallpaperManagerCompat { + + private static final String TAG = "WMCompatVL"; + + private static final String VERSION_PREFIX = "1,"; + private static final String KEY_COLORS = "wallpaper_parsed_colors"; + private static final String EXTRA_RECEIVER = "receiver"; + + private final ArrayList<OnColorsChangedListenerCompat> mListeners = new ArrayList<>(); + + private final Context mContext; + private WallpaperColorsCompat mColorsCompat; + + WallpaperManagerCompatVL(Context context) { + mContext = context; + + String colors = getDevicePrefs(mContext).getString(KEY_COLORS, ""); + int wallpaperId = -1; + if (colors.startsWith(VERSION_PREFIX)) { + Pair<Integer, WallpaperColorsCompat> storedValue = parseValue(colors); + wallpaperId = storedValue.first; + mColorsCompat = storedValue.second; + } + + if (wallpaperId == -1 || wallpaperId != getWallpaperId(context)) { + reloadColors(); + } + context.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + reloadColors(); + } + }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED)); + } + + @Nullable + @Override + public WallpaperColorsCompat getWallpaperColors(int which) { + return which == FLAG_SYSTEM ? mColorsCompat : null; + } + + @Override + public void addOnColorsChangedListener(OnColorsChangedListenerCompat listener) { + mListeners.add(listener); + } + + private void reloadColors() { + ResultReceiver receiver = new ResultReceiver(new Handler()) { + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + handleResult(resultData.getString(KEY_COLORS)); + } + }; + mContext.startService(new Intent(mContext, ColorExtractionService.class) + .putExtra(EXTRA_RECEIVER, receiver)); + } + + private void handleResult(String result) { + getDevicePrefs(mContext).edit().putString(KEY_COLORS, result).apply(); + mColorsCompat = parseValue(result).second; + for (OnColorsChangedListenerCompat listener : mListeners) { + listener.onColorsChanged(mColorsCompat, FLAG_SYSTEM); + } + } + + private static final int getWallpaperId(Context context) { + if (!Utilities.ATLEAST_NOUGAT) { + return -1; + } + return context.getSystemService(WallpaperManager.class).getWallpaperId(FLAG_SYSTEM); + } + + /** + * Parses the stored value and returns the wallpaper id and wallpaper colors. + */ + private static Pair<Integer, WallpaperColorsCompat> parseValue(String value) { + String[] parts = value.split(","); + Integer wallpaperId = Integer.parseInt(parts[1]); + if (parts.length == 2) { + // There is no wallpaper color info present, eg when live wallpaper has no preview. + return Pair.create(wallpaperId, null); + } + + SparseIntArray colorsToOccurrences = new SparseIntArray((parts.length - 2) / 2); + for (int i = 2; i < parts.length; i += 2) { + colorsToOccurrences.put(Integer.parseInt(parts[i]), Integer.parseInt(parts[i + 1])); + } + return Pair.create(wallpaperId, new WallpaperColorsCompat(colorsToOccurrences, false)); + } + + /** + * Intent service to handle color extraction + */ + public static class ColorExtractionService extends IntentService { + private static final int MAX_WALLPAPER_EXTRACTION_AREA = 112 * 112; + + public ColorExtractionService() { + super("ColorExtractionService"); + } + + /** + * Extracts the wallpaper colors and sends the result back through the receiver. + */ + @Override + protected void onHandleIntent(@Nullable Intent intent) { + int wallpaperId = getWallpaperId(this); + + Bitmap bitmap = null; + Drawable drawable = null; + + WallpaperManager wm = WallpaperManager.getInstance(this); + WallpaperInfo info = wm.getWallpaperInfo(); + if (info != null) { + // For live wallpaper, extract colors from thumbnail + drawable = info.loadThumbnail(getPackageManager()); + } else { + if (Utilities.ATLEAST_NOUGAT) { + try (ParcelFileDescriptor fd = wm.getWallpaperFile(FLAG_SYSTEM)) { + BitmapRegionDecoder decoder = BitmapRegionDecoder + .newInstance(fd.getFileDescriptor(), false); + + int requestedArea = decoder.getWidth() * decoder.getHeight(); + BitmapFactory.Options options = new BitmapFactory.Options(); + + if (requestedArea > MAX_WALLPAPER_EXTRACTION_AREA) { + double areaRatio = + (double) requestedArea / MAX_WALLPAPER_EXTRACTION_AREA; + double nearestPowOf2 = + Math.floor(Math.log(areaRatio) / (2 * Math.log(2))); + options.inSampleSize = (int) Math.pow(2, nearestPowOf2); + } + Rect region = new Rect(0, 0, decoder.getWidth(), decoder.getHeight()); + bitmap = decoder.decodeRegion(region, options); + decoder.recycle(); + } catch (IOException | NullPointerException e) { + Log.e(TAG, "Fetching partial bitmap failed, trying old method", e); + } + } + if (bitmap == null) { + drawable = wm.getDrawable(); + } + } + + if (drawable != null) { + // Calculate how big the bitmap needs to be. + // This avoids unnecessary processing and allocation inside Palette. + final int requestedArea = drawable.getIntrinsicWidth() * + drawable.getIntrinsicHeight(); + double scale = 1; + if (requestedArea > MAX_WALLPAPER_EXTRACTION_AREA) { + scale = Math.sqrt(MAX_WALLPAPER_EXTRACTION_AREA / (double) requestedArea); + } + bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * scale), + (int) (drawable.getIntrinsicHeight() * scale), Bitmap.Config.ARGB_8888); + final Canvas bmpCanvas = new Canvas(bitmap); + drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight()); + drawable.draw(bmpCanvas); + } + + String value = VERSION_PREFIX + wallpaperId; + + if (bitmap != null) { + Palette palette = Palette.from(bitmap).generate(); + bitmap.recycle(); + + StringBuilder builder = new StringBuilder(value); + for (Palette.Swatch swatch : palette.getSwatches()) { + builder.append(',') + .append(swatch.getRgb()) + .append(',') + .append(swatch.getPopulation()); + } + value = builder.toString(); + } + + ResultReceiver receiver = intent.getParcelableExtra(EXTRA_RECEIVER); + Bundle result = new Bundle(); + result.putString(KEY_COLORS, value); + receiver.send(0, result); + } + } +} diff --git a/src/com/android/launcher3/compat/WallpaperManagerCompatVOMR1.java b/src/com/android/launcher3/compat/WallpaperManagerCompatVOMR1.java new file mode 100644 index 000000000..c74ccc0cc --- /dev/null +++ b/src/com/android/launcher3/compat/WallpaperManagerCompatVOMR1.java @@ -0,0 +1,109 @@ +/* + * 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.compat; + +import android.annotation.TargetApi; +import android.app.WallpaperManager; +import android.content.Context; +import android.graphics.Color; +import android.os.Build; +import android.support.annotation.Nullable; +import android.util.Log; +import android.util.Pair; +import android.util.SparseIntArray; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.List; + +@TargetApi(Build.VERSION_CODES.O) +public class WallpaperManagerCompatVOMR1 extends WallpaperManagerCompat { + + private static final String TAG = "WMCompatVOMR1"; + + private final WallpaperManager mWm; + + private final Class mOCLClass; + private final Method mAddOCLMethod; + + private final Method mWCGetMethod; + private final Method mWCGetColorsMethod; + private final Method mWCSupportsDarkTextMethod; + + WallpaperManagerCompatVOMR1(Context context) throws Exception { + mWm = context.getSystemService(WallpaperManager.class); + + mOCLClass = Class.forName("android.app.WallpaperManager$OnColorsChangedListener"); + mAddOCLMethod = WallpaperManager.class.getDeclaredMethod( + "addOnColorsChangedListener", mOCLClass); + + mWCGetMethod = WallpaperManager.class.getDeclaredMethod("getWallpaperColors", int.class); + Class wallpaperColorsClass = mWCGetMethod.getReturnType(); + mWCGetColorsMethod = wallpaperColorsClass.getDeclaredMethod("getColors"); + mWCSupportsDarkTextMethod = wallpaperColorsClass.getDeclaredMethod("supportsDarkText"); + } + + @Nullable + @Override + public WallpaperColorsCompat getWallpaperColors(int which) { + try { + return convertColorsObject(mWCGetMethod.invoke(mWm, which)); + } catch (Exception e) { + Log.e(TAG, "Error calling wallpaper API", e); + return null; + } + } + + @Override + public void addOnColorsChangedListener(final OnColorsChangedListenerCompat listener) { + Object onChangeListener = Proxy.newProxyInstance( + WallpaperManager.class.getClassLoader(), + new Class[]{mOCLClass}, + new InvocationHandler() { + @Override + public Object invoke(Object o, Method method, Object[] objects) + throws Throwable { + String methodName = method.getName(); + if ("onColorsChanged".equals(methodName)) { + listener.onColorsChanged( + convertColorsObject(objects[0]), (Integer) objects[1]); + } else if ("toString".equals(methodName)) { + return listener.toString(); + } + return null; + } + }); + try { + mAddOCLMethod.invoke(mWm, onChangeListener); + } catch (Exception e) { + Log.e(TAG, "Error calling wallpaper API", e); + } + } + + private WallpaperColorsCompat convertColorsObject(Object colors) throws Exception { + if (colors == null) { + return null; + } + List<Pair<Color, Integer>> list = (List) mWCGetColorsMethod.invoke(colors); + boolean supportsDarkText = (Boolean) mWCSupportsDarkTextMethod.invoke(colors); + SparseIntArray colorMap = new SparseIntArray(list.size()); + for (Pair<Color, Integer> color : list) { + colorMap.put(color.first.toArgb(), color.second); + } + return new WallpaperColorsCompat(colorMap, supportsDarkText); + } +} diff --git a/src/com/android/launcher3/discovery/AppDiscoveryItem.java b/src/com/android/launcher3/discovery/AppDiscoveryItem.java index 09c91acc6..2e48b25e0 100644 --- a/src/com/android/launcher3/discovery/AppDiscoveryItem.java +++ b/src/com/android/launcher3/discovery/AppDiscoveryItem.java @@ -16,7 +16,6 @@ package com.android.launcher3.discovery; -import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java index 09592a846..29789c82c 100644 --- a/src/com/android/launcher3/dragndrop/AddItemActivity.java +++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java @@ -28,6 +28,7 @@ import android.appwidget.AppWidgetManager; import android.content.ClipData; import android.content.ClipDescription; import android.content.Intent; +import android.content.pm.LauncherApps.PinItemRequest; import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Point; @@ -50,7 +51,7 @@ import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.compat.AppWidgetManagerCompat; -import com.android.launcher3.compat.PinItemRequestCompat; +import com.android.launcher3.compat.LauncherAppsCompatVO; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; @@ -60,7 +61,7 @@ import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.WidgetHostViewLoader; import com.android.launcher3.widget.WidgetImageView; -@TargetApi(Build.VERSION_CODES.N_MR1) +@TargetApi(Build.VERSION_CODES.O) public class AddItemActivity extends BaseActivity implements OnLongClickListener, OnTouchListener { private static final int SHADOW_SIZE = 10; @@ -70,7 +71,7 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener private final PointF mLastTouchPos = new PointF(); - private PinItemRequestCompat mRequest; + private PinItemRequest mRequest; private LauncherAppState mApp; private InvariantDeviceProfile mIdp; @@ -87,7 +88,7 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mRequest = PinItemRequestCompat.getPinItemRequest(getIntent()); + mRequest = LauncherAppsCompatVO.getPinItemRequest(getIntent()); if (mRequest == null) { finish(); return; @@ -101,9 +102,9 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener mDeviceProfile = mIdp.getDeviceProfile(getApplicationContext()); setContentView(R.layout.add_item_confirmation_activity); - mWidgetCell = (LivePreviewWidgetCell) findViewById(R.id.widget_cell); + mWidgetCell = findViewById(R.id.widget_cell); - if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) { + if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) { setupShortcut(); } else { if (!setupWidget()) { @@ -226,7 +227,7 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener * Called when place-automatically button is clicked. */ public void onPlaceAutomaticallyClick(View v) { - if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) { + if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) { InstallShortcutReceiver.queueShortcut( new ShortcutInfoCompat(mRequest.getShortcutInfo()), this); logCommand(Action.Command.CONFIRM); diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index 7178c5e8d..be5f01adb 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -27,6 +27,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Rect; import android.graphics.Region; +import android.support.v4.graphics.ColorUtils; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -54,10 +55,12 @@ import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dynamicui.WallpaperColorInfo; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.logging.LoggerUtils; +import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.TouchController; import com.android.launcher3.widget.WidgetsBottomSheet; @@ -72,9 +75,6 @@ public class DragLayer extends InsettableFrameLayout { public static final int ANIMATION_END_DISAPPEAR = 0; public static final int ANIMATION_END_REMAIN_VISIBLE = 2; - // Scrim color without any alpha component. - private static final int SCRIM_COLOR = Color.BLACK & 0x00FFFFFF; - private final int[] mTmpXY = new int[2]; @Thunk DragController mDragController; @@ -107,6 +107,7 @@ public class DragLayer extends InsettableFrameLayout { // Related to adjacent page hints private final Rect mScrollChildPosition = new Rect(); private final ViewGroupFocusHelper mFocusIndicatorHelper; + private final WallpaperColorInfo mWallpaperColorInfo; // Related to pinch-to-go-to-overview gesture. private PinchToOverviewListener mPinchListener = null; @@ -130,6 +131,7 @@ public class DragLayer extends InsettableFrameLayout { mIsRtl = Utilities.isRtl(getResources()); mFocusIndicatorHelper = new ViewGroupFocusHelper(this); + mWallpaperColorInfo = WallpaperColorInfo.getInstance(getContext()); } public void setup(Launcher launcher, DragController dragController, @@ -451,7 +453,8 @@ public class DragLayer extends InsettableFrameLayout { @Override public void setInsets(Rect insets) { super.setInsets(insets); - setBackgroundResource(insets.top == 0 ? 0 : R.drawable.workspace_bg); + setBackground(insets.top == 0 ? null + : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim)); } @Override @@ -876,7 +879,10 @@ public class DragLayer extends InsettableFrameLayout { getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect); canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE); } - canvas.drawColor((alpha << 24) | SCRIM_COLOR); + // for super light wallpaper it needs to be darken for contrast to workspace + // for dark wallpapers the text is white so darkening works as well + int color = ColorUtils.compositeColors(0x66000000, mWallpaperColorInfo.getMainColor()); + canvas.drawColor(ColorUtils.setAlphaComponent(color, alpha)); canvas.restore(); } diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java index 230fa2d4a..9433aadc7 100644 --- a/src/com/android/launcher3/dragndrop/DragOptions.java +++ b/src/com/android/launcher3/dragndrop/DragOptions.java @@ -17,8 +17,6 @@ package com.android.launcher3.dragndrop; import android.graphics.Point; -import android.support.annotation.CallSuper; -import android.view.View; import com.android.launcher3.DropTarget; diff --git a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java index 36a0292da..e36f607de 100644 --- a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java +++ b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java @@ -4,7 +4,6 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import android.widget.FrameLayout; import android.widget.RemoteViews; diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java index df0c47cc4..f9f4e50f7 100644 --- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java +++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java @@ -16,11 +16,14 @@ package com.android.launcher3.dragndrop; +import android.annotation.TargetApi; import android.appwidget.AppWidgetManager; import android.content.ClipDescription; import android.content.Intent; +import android.content.pm.LauncherApps.PinItemRequest; import android.graphics.Point; import android.graphics.Rect; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -40,7 +43,7 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.PendingAddItemInfo; import com.android.launcher3.R; -import com.android.launcher3.compat.PinItemRequestCompat; +import com.android.launcher3.Utilities; import com.android.launcher3.folder.Folder; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; @@ -55,6 +58,7 @@ import java.util.UUID; * {@link DragSource} for handling drop from a different window. This object is initialized * in the source window and is passed on to the Launcher activity as an Intent extra. */ +@TargetApi(Build.VERSION_CODES.O) public class PinItemDragListener implements Parcelable, View.OnDragListener, DragSource, DragOptions.PreDragCondition { @@ -63,7 +67,7 @@ public class PinItemDragListener private static final String MIME_TYPE_PREFIX = "com.android.launcher3.drag_and_drop/"; public static final String EXTRA_PIN_ITEM_DRAG_LISTENER = "pin_item_drag_listener"; - private final PinItemRequestCompat mRequest; + private final PinItemRequest mRequest; // Position of preview relative to the touch location private final Rect mPreviewRect; @@ -78,7 +82,7 @@ public class PinItemDragListener private DragController mDragController; private long mDragStartTime; - public PinItemDragListener(PinItemRequestCompat request, Rect previewRect, + public PinItemDragListener(PinItemRequest request, Rect previewRect, int previewBitmapWidth, int previewViewWidth) { mRequest = request; mPreviewRect = previewRect; @@ -88,7 +92,7 @@ public class PinItemDragListener } private PinItemDragListener(Parcel parcel) { - mRequest = PinItemRequestCompat.CREATOR.createFromParcel(parcel); + mRequest = PinItemRequest.CREATOR.createFromParcel(parcel); mPreviewRect = Rect.CREATOR.createFromParcel(parcel); mPreviewBitmapWidth = parcel.readInt(); mPreviewViewWidth = parcel.readInt(); @@ -146,7 +150,7 @@ public class PinItemDragListener } final PendingAddItemInfo item; - if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) { + if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) { item = new PendingAddShortcutInfo( new PinShortcutRequestActivityInfo(mRequest, mLauncher)); } else { @@ -177,7 +181,7 @@ public class PinItemDragListener // across windows, using drag position here give a good estimate for relative position // to source window. PendingItemDragHelper dragHelper = new PendingItemDragHelper(view); - if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_APPWIDGET) { + if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_APPWIDGET) { dragHelper.setPreview(getPreview(mRequest)); } @@ -271,7 +275,7 @@ public class PinItemDragListener } } - public static RemoteViews getPreview(PinItemRequestCompat request) { + public static RemoteViews getPreview(PinItemRequest request) { Bundle extras = request.getExtras(); if (extras != null && extras.get(AppWidgetManager.EXTRA_APPWIDGET_PREVIEW) instanceof RemoteViews) { @@ -281,6 +285,9 @@ public class PinItemDragListener } public static boolean handleDragRequest(Launcher launcher, Intent intent) { + if (!Utilities.isAtLeastO()) { + return false; + } if (intent == null || !Intent.ACTION_MAIN.equals(intent.getAction())) { return false; } diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java index bb5ac5b02..52abbc766 100644 --- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java +++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java @@ -21,6 +21,7 @@ import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.pm.LauncherApps; +import android.content.pm.LauncherApps.PinItemRequest; import android.content.pm.ShortcutInfo; import android.graphics.drawable.Drawable; import android.os.Build; @@ -30,26 +31,25 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; -import com.android.launcher3.compat.LauncherAppsCompat; -import com.android.launcher3.compat.PinItemRequestCompat; +import com.android.launcher3.compat.LauncherAppsCompatVO; import com.android.launcher3.compat.ShortcutConfigActivityInfo; /** * Extension of ShortcutConfigActivityInfo to be used in the confirmation prompt for pin item * request. */ -@TargetApi(Build.VERSION_CODES.N_MR1) +@TargetApi(Build.VERSION_CODES.O) class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo { // Class name used in the target component, such that it will never represent an // actual existing class. private static final String DUMMY_COMPONENT_CLASS = "pinned-shortcut"; - private final PinItemRequestCompat mRequest; + private final PinItemRequest mRequest; private final ShortcutInfo mInfo; private final Context mContext; - public PinShortcutRequestActivityInfo(PinItemRequestCompat request, Context context) { + public PinShortcutRequestActivityInfo(PinItemRequest request, Context context) { super(new ComponentName(request.getShortcutInfo().getPackage(), DUMMY_COMPONENT_CLASS), request.getShortcutInfo().getUserHandle()); mRequest = request; @@ -80,7 +80,7 @@ class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo { Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT + mContext.getResources().getInteger(R.integer.config_overlayTransitionTime) / 2; // Delay the actual accept() call until the drop animation is complete. - return LauncherAppsCompat.createShortcutInfoFromPinItemRequest( + return LauncherAppsCompatVO.createShortcutInfoFromPinItemRequest( mContext, mRequest, duration); } diff --git a/src/com/android/launcher3/dragndrop/PinWidgetFlowHandler.java b/src/com/android/launcher3/dragndrop/PinWidgetFlowHandler.java index b6da6ad33..9f617e4f6 100644 --- a/src/com/android/launcher3/dragndrop/PinWidgetFlowHandler.java +++ b/src/com/android/launcher3/dragndrop/PinWidgetFlowHandler.java @@ -16,15 +16,17 @@ package com.android.launcher3.dragndrop; +import android.annotation.TargetApi; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; +import android.content.pm.LauncherApps.PinItemRequest; +import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; -import com.android.launcher3.compat.PinItemRequestCompat; import com.android.launcher3.widget.WidgetAddFlowHandler; /** @@ -33,18 +35,19 @@ import com.android.launcher3.widget.WidgetAddFlowHandler; * No config activity is shown even if it is defined in widget config. And a callback is sent when * the widget is bound. */ +@TargetApi(Build.VERSION_CODES.O) public class PinWidgetFlowHandler extends WidgetAddFlowHandler implements Parcelable { - private final PinItemRequestCompat mRequest; + private final PinItemRequest mRequest; - public PinWidgetFlowHandler(AppWidgetProviderInfo providerInfo, PinItemRequestCompat request) { + public PinWidgetFlowHandler(AppWidgetProviderInfo providerInfo, PinItemRequest request) { super(providerInfo); mRequest = request; } protected PinWidgetFlowHandler(Parcel parcel) { super(parcel); - mRequest = PinItemRequestCompat.CREATOR.createFromParcel(parcel); + mRequest = PinItemRequest.CREATOR.createFromParcel(parcel); } @Override diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionAlgorithm.java b/src/com/android/launcher3/dynamicui/ColorExtractionAlgorithm.java new file mode 100644 index 000000000..21d5b2744 --- /dev/null +++ b/src/com/android/launcher3/dynamicui/ColorExtractionAlgorithm.java @@ -0,0 +1,760 @@ +/* + * 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.dynamicui; + +import android.content.Context; +import android.graphics.Color; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.graphics.ColorUtils; +import android.util.Log; +import android.util.Pair; +import android.util.Range; +import android.util.SparseIntArray; + +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.compat.WallpaperColorsCompat; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Implementation of tonal color extraction + **/ +public class ColorExtractionAlgorithm { + + public static ColorExtractionAlgorithm newInstance(Context context) { + return Utilities.getOverrideObject(ColorExtractionAlgorithm.class, + context.getApplicationContext(), R.string.color_extraction_impl_class); + } + + private static final String TAG = "Tonal"; + + // Used for tonal palette fitting + private static final float FIT_WEIGHT_H = 1.0f; + private static final float FIT_WEIGHT_S = 1.0f; + private static final float FIT_WEIGHT_L = 10.0f; + + // When extracting the main color, only consider colors + // present in at least MIN_COLOR_OCCURRENCE of the image + private static final float MIN_COLOR_OCCURRENCE = 0.1f; + + // Temporary variable to avoid allocations + private final float[] mTmpHSL = new float[3]; + + public @Nullable Pair<Integer, Integer> extractInto(WallpaperColorsCompat wallpaperColors) { + if (wallpaperColors == null) { + return null; + } + + SparseIntArray colorsArray = wallpaperColors.getColors(); + if (colorsArray.size() == 0) { + return null; + } + // Tonal is not really a sort, it takes a color from the extracted + // palette and finds a best fit amongst a collection of pre-defined + // palettes. The best fit is tweaked to be closer to the source color + // and replaces the original palette + + List<Pair<Integer, Integer>> colors = new ArrayList<>(colorsArray.size()); + for (int i = colorsArray.size() - 1; i >= 0; i--) { + colors.add(Pair.create(colorsArray.keyAt(i), colorsArray.valueAt(i))); + } + + // First find the most representative color in the image + populationSort(colors); + // Calculate total + int total = 0; + for (Pair<Integer, Integer> weightedColor : colors) { + total += weightedColor.second; + } + + // Get bright colors that occur often enough in this image + Pair<Integer, Integer> bestColor = null; + float[] hsl = new float[3]; + for (Pair<Integer, Integer> weightedColor : colors) { + float colorOccurrence = weightedColor.second / (float) total; + if (colorOccurrence < MIN_COLOR_OCCURRENCE) { + break; + } + + int colorValue = weightedColor.first; + ColorUtils.RGBToHSL(Color.red(colorValue), Color.green(colorValue), + Color.blue(colorValue), hsl); + + // Stop when we find a color that meets our criteria + if (!isBlacklisted(hsl)) { + bestColor = weightedColor; + break; + } + } + + // Fail if not found + if (bestColor == null) { + return null; + } + + int colorValue = bestColor.first; + ColorUtils.RGBToHSL(Color.red(colorValue), Color.green(colorValue), Color.blue(colorValue), + hsl); + + // The Android HSL definition requires the hue to go from 0 to 360 but + // the Material Tonal Palette defines hues from 0 to 1. + hsl[0] /= 360f; + + // Find the palette that contains the closest color + TonalPalette palette = findTonalPalette(hsl[0]); + + if (palette == null) { + Log.w(TAG, "Could not find a tonal palette!"); + return null; + } + + // Figure out what's the main color index in the optimal palette + int fitIndex = bestFit(palette, hsl[0], hsl[1], hsl[2]); + if (fitIndex == -1) { + Log.w(TAG, "Could not find best fit!"); + return null; + } + + // Generate the 10 colors palette by offsetting each one of them + float[] h = fit(palette.h, hsl[0], fitIndex, + Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY); + float[] s = fit(palette.s, hsl[1], fitIndex, 0.0f, 1.0f); + float[] l = fit(palette.l, hsl[2], fitIndex, 0.0f, 1.0f); + + final int textInversionIndex = h.length - 3; + + // best fit + a 2 colors offset + int primaryIndex = fitIndex; + int secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2); + int mainColor = getColorInt(primaryIndex, h, s, l); + int secondaryColor = getColorInt(secondaryIndex, h, s, l); + + return new Pair<>(mainColor, secondaryColor); + } + + private int getColorInt(int fitIndex, float[] h, float[] s, float[] l) { + mTmpHSL[0] = fract(h[fitIndex]) * 360.0f; + mTmpHSL[1] = s[fitIndex]; + mTmpHSL[2] = l[fitIndex]; + return ColorUtils.HSLToColor(mTmpHSL); + } + + /** + * Checks if a given color exists in the blacklist + * @param hsl float array with 3 components (H 0..360, S 0..1 and L 0..1) + * @return true if color should be avoided + */ + private boolean isBlacklisted(float[] hsl) { + for (ColorRange badRange: BLACKLISTED_COLORS) { + if (badRange.containsColor(hsl[0], hsl[1], hsl[2])) { + return true; + } + } + return false; + } + + private static void populationSort(@NonNull List<Pair<Integer, Integer>> wallpaperColors) { + Collections.sort(wallpaperColors, new Comparator<Pair<Integer, Integer>>() { + @Override + public int compare(Pair<Integer, Integer> a, Pair<Integer, Integer> b) { + return b.second - a.second; + } + }); + } + + /** + * Offsets all colors by a delta, clamping values that go beyond what's + * supported on the color space. + * @param data what you want to fit + * @param v how big should be the offset + * @param index which index to calculate the delta against + * @param min minimum accepted value (clamp) + * @param max maximum accepted value (clamp) + * @return new shifted palette + */ + private static float[] fit(float[] data, float v, int index, float min, float max) { + float[] fitData = new float[data.length]; + float delta = v - data[index]; + + for (int i = 0; i < data.length; i++) { + fitData[i] = Utilities.boundToRange(data[i] + delta, min, max); + } + + return fitData; + } + + /** + * Finds the closest color in a palette, given another HSL color + * + * @param palette where to search + * @param h hue + * @param s saturation + * @param l lightness + * @return closest index or -1 if palette is empty. + */ + private static int bestFit(@NonNull TonalPalette palette, float h, float s, float l) { + int minErrorIndex = -1; + float minError = Float.POSITIVE_INFINITY; + + for (int i = 0; i < palette.h.length; i++) { + float error = + FIT_WEIGHT_H * Math.abs(h - palette.h[i]) + + FIT_WEIGHT_S * Math.abs(s - palette.s[i]) + + FIT_WEIGHT_L * Math.abs(l - palette.l[i]); + if (error < minError) { + minError = error; + minErrorIndex = i; + } + } + + return minErrorIndex; + } + + @Nullable + private static TonalPalette findTonalPalette(float h) { + TonalPalette best = null; + float error = Float.POSITIVE_INFINITY; + + for (TonalPalette candidate : TONAL_PALETTES) { + if (h >= candidate.minHue && h <= candidate.maxHue) { + best = candidate; + break; + } + + if (candidate.maxHue > 1.0f && h >= 0.0f && h <= fract(candidate.maxHue)) { + best = candidate; + break; + } + + if (candidate.minHue < 0.0f && h >= fract(candidate.minHue) && h <= 1.0f) { + best = candidate; + break; + } + + if (h <= candidate.minHue && candidate.minHue - h < error) { + best = candidate; + error = candidate.minHue - h; + } else if (h >= candidate.maxHue && h - candidate.maxHue < error) { + best = candidate; + error = h - candidate.maxHue; + } else if (candidate.maxHue > 1.0f && h >= fract(candidate.maxHue) + && h - fract(candidate.maxHue) < error) { + best = candidate; + error = h - fract(candidate.maxHue); + } else if (candidate.minHue < 0.0f && h <= fract(candidate.minHue) + && fract(candidate.minHue) - h < error) { + best = candidate; + error = fract(candidate.minHue) - h; + } + } + + return best; + } + + private static float fract(float v) { + return v - (float) Math.floor(v); + } + + static class TonalPalette { + final float[] h; + final float[] s; + final float[] l; + final float minHue; + final float maxHue; + + TonalPalette(float[] h, float[] s, float[] l) { + this.h = h; + this.s = s; + this.l = l; + + float minHue = Float.POSITIVE_INFINITY; + float maxHue = Float.NEGATIVE_INFINITY; + + for (float v : h) { + minHue = Math.min(v, minHue); + maxHue = Math.max(v, maxHue); + } + + this.minHue = minHue; + this.maxHue = maxHue; + } + } + + // Data definition of Material Design tonal palettes + // When the sort type is set to TONAL, these palettes are used to find + // a best fit. Each palette is defined as 22 HSL colors + private static final TonalPalette[] TONAL_PALETTES = { + new TonalPalette( + new float[]{0.991f, 0.9833333333333333f, 0f, 0f, 0f, 0.01134380453752181f, + 0.015625000000000003f, 0.024193548387096798f, 0.027397260273972573f, + 0.017543859649122865f}, + new float[]{1f, 1f, 1f, 1f, 0.8434782608695652f, 1f, 1f, 1f, 1f, 1f}, + new float[]{0.2f, 0.27450980392156865f, 0.34901960784313724f, + 0.4235294117647059f, 0.5490196078431373f, 0.6254901960784314f, + 0.6862745098039216f, 0.7568627450980392f, 0.8568627450980393f, + 0.9254901960784314f} + ), + new TonalPalette( + new float[]{0.6385767790262171f, 0.6301169590643275f, 0.6223958333333334f, + 0.6151079136690647f, 0.6065400843881856f, 0.5986964618249534f, + 0.5910746812386157f, 0.5833333333333334f, 0.5748031496062993f, + 0.5582010582010583f}, + new float[]{1f, 1f, 0.9014084507042253f, 0.8128654970760234f, + 0.7979797979797981f, 0.7816593886462883f, 0.778723404255319f, + 1f, 1f, 1f}, + new float[]{0.17450980392156862f, 0.2235294117647059f, 0.2784313725490196f, + 0.3352941176470588f, 0.388235294117647f, 0.44901960784313727f, + 0.5392156862745098f, 0.6509803921568628f, 0.7509803921568627f, + 0.8764705882352941f} + ), + new TonalPalette( + new float[]{0.5669934640522876f, 0.5748031496062993f, + 0.5595238095238095f, 0.5473118279569893f, 0.5393258426966292f, + 0.5315955766192734f, 0.524031007751938f, 0.5154711673699016f, + 0.508080808080808f, 0.5f}, + new float[]{1f, 1f, 1f, 1f, 1f, 1f, 0.8847736625514403f, 1f, 1f, 1f}, + new float[]{0.2f, 0.24901960784313726f, 0.27450980392156865f, + 0.30392156862745096f, 0.34901960784313724f, 0.4137254901960784f, + 0.47647058823529415f, 0.5352941176470588f, 0.6764705882352942f, 0.8f} + ), + new TonalPalette( + new float[]{0.5082304526748972f, 0.5069444444444444f, 0.5f, 0.5f, + 0.5f, 0.48724954462659376f, 0.4800347222222222f, + 0.4755134281200632f, 0.4724409448818897f, 0.4671052631578947f}, + new float[]{1f, 0.8888888888888887f, 0.9242424242424242f, 1f, 1f, + 0.8133333333333332f, 0.7868852459016393f, 1f, 1f, 1f}, + new float[]{0.1588235294117647f, 0.21176470588235297f, + 0.25882352941176473f, 0.3f, 0.34901960784313724f, + 0.44117647058823534f, 0.5215686274509804f, 0.5862745098039216f, + 0.7509803921568627f, 0.8509803921568627f} + ), + new TonalPalette( + new float[]{0.3333333333333333f, 0.3333333333333333f, + 0.34006734006734f, 0.34006734006734f, 0.34006734006734f, + 0.34259259259259256f, 0.3475783475783476f, 0.34767025089605735f, + 0.3467741935483871f, 0.3703703703703704f}, + new float[]{0.6703296703296703f, 0.728813559322034f, + 0.5657142857142856f, 0.5076923076923077f, 0.3944223107569721f, + 0.6206896551724138f, 0.8931297709923666f, 1f, 1f, 1f}, + new float[]{0.1784313725490196f, 0.23137254901960785f, + 0.3431372549019608f, 0.38235294117647056f, 0.49215686274509807f, + 0.6588235294117647f, 0.7431372549019608f, 0.8176470588235294f, + 0.8784313725490196f, 0.9294117647058824f} + ), + new TonalPalette( + new float[]{0.162280701754386f, 0.15032679738562088f, + 0.15879265091863518f, 0.16236559139784948f, 0.17443868739205526f, + 0.17824074074074076f, 0.18674698795180725f, + 0.18692449355432778f, 0.1946778711484594f, 0.18604651162790695f}, + new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, + new float[]{0.14901960784313725f, 0.2f, 0.24901960784313726f, + 0.30392156862745096f, 0.3784313725490196f, 0.4235294117647059f, + 0.48823529411764705f, 0.6450980392156863f, 0.7666666666666666f, + 0.8313725490196078f} + ), + new TonalPalette( + new float[]{0.10619469026548674f, 0.11924686192468618f, + 0.13046448087431692f, 0.14248366013071895f, 0.1506024096385542f, + 0.16220238095238093f, 0.16666666666666666f, + 0.16666666666666666f, 0.162280701754386f, 0.15686274509803924f}, + new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, + new float[]{0.44313725490196076f, 0.46862745098039216f, + 0.47843137254901963f, 0.5f, 0.5117647058823529f, + 0.5607843137254902f, 0.6509803921568628f, 0.7509803921568627f, + 0.8509803921568627f, 0.9f} + ), + new TonalPalette( + new float[]{0.03561253561253561f, 0.05098039215686275f, + 0.07516339869281045f, 0.09477124183006536f, 0.1150326797385621f, + 0.134640522875817f, 0.14640522875816991f, 0.1582397003745319f, + 0.15773809523809523f, 0.15359477124183002f}, + new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, + new float[]{0.4588235294117647f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, + 0.5f, 0.6509803921568628f, 0.7803921568627451f, 0.9f} + ), + new TonalPalette( + new float[]{0.9596491228070175f, 0.9593837535014005f, + 0.9514767932489452f, 0.943859649122807f, 0.9396825396825397f, + 0.9395424836601307f, 0.9393939393939394f, 0.9362745098039216f, + 0.9754098360655739f, 0.9824561403508771f}, + new float[]{0.84070796460177f, 0.8206896551724138f, + 0.7979797979797981f, 0.7661290322580644f, 0.9051724137931036f, + 1f, 1f, 1f, 1f, 1f}, + new float[]{0.22156862745098038f, 0.2843137254901961f, + 0.388235294117647f, 0.48627450980392156f, 0.5450980392156863f, + 0.6f, 0.6764705882352942f, 0.8f, 0.8803921568627451f, + 0.9254901960784314f} + ), + new TonalPalette( + new float[]{0.841025641025641f, 0.8333333333333334f, + 0.8285256410256411f, 0.821522309711286f, 0.8083333333333333f, + 0.8046594982078853f, 0.8005822416302766f, 0.7842377260981912f, + 0.7771084337349398f, 0.7747747747747749f}, + new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, + 0.737142857142857f, 0.6434108527131781f, 0.46835443037974644f}, + new float[]{0.12745098039215685f, 0.15490196078431373f, + 0.20392156862745098f, 0.24901960784313726f, 0.3137254901960784f, + 0.36470588235294116f, 0.44901960784313727f, + 0.6568627450980392f, 0.7470588235294118f, 0.8450980392156863f} + ), + new TonalPalette( + new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f}, + new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f}, + new float[]{0.14901960784313725f, 0.2f, 0.2980392156862745f, 0.4f, + 0.4980392156862745f, 0.6196078431372549f, 0.7176470588235294f, + 0.8196078431372549f, 0.9176470588235294f, 0.9490196078431372f} + ), + new TonalPalette( + new float[]{0.955952380952381f, 0.9681069958847737f, + 0.9760479041916167f, 0.9873563218390804f, 0f, 0f, + 0.009057971014492771f, 0.026748971193415648f, + 0.041666666666666616f, 0.05303030303030304f}, + new float[]{1f, 0.8350515463917526f, 0.6929460580912863f, + 0.6387665198237885f, 0.6914893617021276f, 0.7583892617449666f, + 0.8070175438596495f, 0.9310344827586209f, 1f, 1f}, + new float[]{0.27450980392156865f, 0.3803921568627451f, + 0.4725490196078432f, 0.5549019607843138f, 0.6313725490196078f, + 0.707843137254902f, 0.7764705882352941f, 0.8294117647058823f, + 0.9058823529411765f, 0.9568627450980391f} + ), + new TonalPalette( + new float[]{0.7514619883040936f, 0.7679738562091503f, + 0.7802083333333333f, 0.7844311377245509f, 0.796875f, + 0.8165618448637316f, 0.8487179487179487f, 0.8582375478927203f, + 0.8562091503267975f, 0.8666666666666667f}, + new float[]{1f, 1f, 0.8163265306122449f, 0.6653386454183268f, + 0.7547169811320753f, 0.929824561403509f, 0.9558823529411766f, + 0.9560439560439562f, 1f, 1f}, + new float[]{0.2235294117647059f, 0.3f, 0.38431372549019605f, + 0.492156862745098f, 0.5843137254901961f, 0.6647058823529411f, + 0.7333333333333334f, 0.8215686274509804f, 0.9f, + 0.9411764705882353f} + ), + new TonalPalette( + new float[]{0.6666666666666666f, 0.6666666666666666f, + 0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f, + 0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f, + 0.6666666666666666f, 0.6666666666666666f}, + new float[]{0.24590163934426232f, 0.17880794701986752f, + 0.14606741573033713f, 0.13761467889908252f, 0.14893617021276592f, + 0.16756756756756758f, 0.20312500000000017f, + 0.26086956521739135f, 0.29999999999999966f, 0.5000000000000004f}, + new float[]{0.2392156862745098f, 0.296078431372549f, + 0.34901960784313724f, 0.4274509803921569f, 0.5392156862745098f, + 0.6372549019607843f, 0.7490196078431373f, 0.8196078431372549f, + 0.8823529411764706f, 0.9372549019607843f} + ), + new TonalPalette( + new float[]{0.9678571428571429f, 0.9944812362030905f, 0f, 0f, + 0.0047348484848484815f, 0.00316455696202532f, 0f, + 0.9980392156862745f, 0.9814814814814816f, 0.9722222222222221f}, + new float[]{1f, 0.7023255813953488f, 0.6638655462184874f, + 0.6521739130434782f, 0.7719298245614035f, 0.8315789473684211f, + 0.6867469879518071f, 0.7264957264957265f, 0.8181818181818182f, + 0.8181818181818189f}, + new float[]{0.27450980392156865f, 0.4215686274509804f, + 0.4666666666666667f, 0.503921568627451f, 0.5529411764705883f, + 0.6274509803921569f, 0.6745098039215687f, 0.7705882352941176f, + 0.892156862745098f, 0.9568627450980391f} + ), + new TonalPalette( + new float[]{0.9052287581699346f, 0.9112021857923498f, 0.9270152505446624f, + 0.9343137254901961f, 0.9391534391534391f, 0.9437984496124031f, + 0.943661971830986f, 0.9438943894389439f, 0.9426229508196722f, + 0.9444444444444444f}, + new float[]{1f, 0.8133333333333332f, 0.7927461139896375f, 0.7798165137614679f, + 0.7777777777777779f, 0.8190476190476191f, 0.8255813953488372f, + 0.8211382113821142f, 0.8133333333333336f, 0.8000000000000006f}, + new float[]{0.2f, 0.29411764705882354f, 0.3784313725490196f, + 0.42745098039215684f, 0.4764705882352941f, 0.5882352941176471f, + 0.6627450980392157f, 0.7588235294117647f, 0.8529411764705882f, + 0.9411764705882353f} + ), + new TonalPalette( + new float[]{0.6884057971014492f, 0.6974789915966387f, 0.7079889807162534f, + 0.7154471544715447f, 0.7217741935483872f, 0.7274143302180687f, + 0.7272727272727273f, 0.7258064516129031f, 0.7252252252252251f, + 0.7333333333333333f}, + new float[]{0.8214285714285715f, 0.6878612716763006f, 0.6080402010050251f, + 0.5774647887323943f, 0.5391304347826086f, 0.46724890829694316f, + 0.4680851063829788f, 0.462686567164179f, 0.45679012345678977f, + 0.4545454545454551f}, + new float[]{0.2196078431372549f, 0.33921568627450976f, 0.39019607843137255f, + 0.4176470588235294f, 0.45098039215686275f, + 0.5509803921568628f, 0.6313725490196078f, 0.7372549019607844f, + 0.8411764705882353f, 0.9352941176470588f} + ), + new TonalPalette( + new float[]{0.6470588235294118f, 0.6516666666666667f, 0.6464174454828661f, + 0.6441441441441442f, 0.6432748538011696f, 0.6416666666666667f, + 0.6402439024390243f, 0.6412429378531074f, 0.6435185185185186f, + 0.6428571428571429f}, + new float[]{0.8095238095238095f, 0.6578947368421053f, 0.5721925133689839f, + 0.5362318840579711f, 0.5f, 0.4424778761061947f, 0.44086021505376327f, + 0.44360902255639095f, + 0.4499999999999997f, 0.4375000000000006f}, + new float[]{0.16470588235294117f, 0.2980392156862745f, 0.36666666666666664f, + 0.40588235294117647f, 0.44705882352941173f, + 0.5568627450980392f, 0.6352941176470588f, 0.7392156862745098f, + 0.8431372549019608f, 0.9372549019607843f} + ), + new TonalPalette( + new float[]{0.46732026143790845f, 0.4718614718614719f, 0.4793650793650794f, + 0.48071625344352614f, 0.4829683698296837f, 0.484375f, + 0.4841269841269842f, 0.48444444444444457f, 0.48518518518518516f, + 0.4907407407407408f}, + new float[]{1f, 1f, 1f, 1f, 1f, 0.6274509803921569f, 0.41832669322709176f, + 0.41899441340782106f, 0.4128440366972478f, + 0.4090909090909088f}, + new float[]{0.1f, 0.15098039215686274f, 0.20588235294117646f, + 0.2372549019607843f, 0.26862745098039215f, 0.4f, 0.5078431372549019f, + 0.6490196078431372f, 0.7862745098039216f, 0.9137254901960784f} + ), + new TonalPalette( + new float[]{0.5444444444444444f, 0.5555555555555556f, 0.5555555555555556f, + 0.553763440860215f, 0.5526315789473684f, 0.5555555555555556f, + 0.5555555555555555f, 0.5555555555555556f, 0.5512820512820514f, + 0.5666666666666667f}, + new float[]{0.24590163934426232f, 0.19148936170212766f, 0.1791044776119403f, + 0.18343195266272191f, 0.18446601941747576f, + 0.1538461538461539f, 0.15625000000000003f, 0.15328467153284678f, + 0.15662650602409653f, 0.151515151515151f}, + new float[]{0.1196078431372549f, 0.1843137254901961f, 0.2627450980392157f, + 0.33137254901960783f, 0.403921568627451f, 0.5411764705882354f, + 0.6235294117647059f, 0.7313725490196079f, 0.8372549019607843f, + 0.9352941176470588f} + ), + new TonalPalette( + new float[]{0.022222222222222223f, 0.02469135802469136f, 0.031249999999999997f, + 0.03947368421052631f, 0.04166666666666668f, + 0.043650793650793655f, 0.04411764705882352f, 0.04166666666666652f, + 0.04444444444444459f, 0.05555555555555529f}, + new float[]{0.33333333333333337f, 0.2783505154639175f, 0.2580645161290323f, + 0.25675675675675674f, 0.2528735632183908f, 0.17500000000000002f, + 0.15315315315315312f, 0.15189873417721522f, + 0.15789473684210534f, 0.15789473684210542f}, + new float[]{0.08823529411764705f, 0.19019607843137254f, 0.2431372549019608f, + 0.2901960784313725f, 0.3411764705882353f, 0.47058823529411764f, + 0.5647058823529412f, 0.6901960784313725f, 0.8137254901960784f, + 0.9254901960784314f} + ), + new TonalPalette( + new float[]{0.050884955752212385f, 0.07254901960784313f, 0.0934640522875817f, + 0.10457516339869281f, 0.11699346405228758f, + 0.1255813953488372f, 0.1268939393939394f, 0.12533333333333332f, + 0.12500000000000003f, 0.12777777777777777f}, + new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, + new float[]{0.44313725490196076f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5784313725490196f, + 0.6549019607843137f, 0.7549019607843137f, 0.8509803921568627f, + 0.9411764705882353f} + ) + }; + + @SuppressWarnings("WeakerAccess") + static final ColorRange[] BLACKLISTED_COLORS = new ColorRange[] { + + // Red + new ColorRange( + new Range<>(0f, 20f) /* H */, + new Range<>(0.7f, 1f) /* S */, + new Range<>(0.21f, 0.79f)) /* L */, + new ColorRange( + new Range<>(0f, 20f), + new Range<>(0.3f, 0.7f), + new Range<>(0.355f, 0.653f)), + + // Red Orange + new ColorRange( + new Range<>(20f, 40f), + new Range<>(0.7f, 1f), + new Range<>(0.28f, 0.643f)), + new ColorRange( + new Range<>(20f, 40f), + new Range<>(0.3f, 0.7f), + new Range<>(0.414f, 0.561f)), + new ColorRange( + new Range<>(20f, 40f), + new Range<>(0f, 3f), + new Range<>(0.343f, 0.584f)), + + // Orange + new ColorRange( + new Range<>(40f, 60f), + new Range<>(0.7f, 1f), + new Range<>(0.173f, 0.349f)), + new ColorRange( + new Range<>(40f, 60f), + new Range<>(0.3f, 0.7f), + new Range<>(0.233f, 0.427f)), + new ColorRange( + new Range<>(40f, 60f), + new Range<>(0f, 0.3f), + new Range<>(0.231f, 0.484f)), + + // Yellow 60 + new ColorRange( + new Range<>(60f, 80f), + new Range<>(0.7f, 1f), + new Range<>(0.488f, 0.737f)), + new ColorRange( + new Range<>(60f, 80f), + new Range<>(0.3f, 0.7f), + new Range<>(0.673f, 0.837f)), + + // Yellow Green 80 + new ColorRange( + new Range<>(80f, 100f), + new Range<>(0.7f, 1f), + new Range<>(0.469f, 0.61f)), + + // Yellow green 100 + new ColorRange( + new Range<>(100f, 120f), + new Range<>(0.7f, 1f), + new Range<>(0.388f, 0.612f)), + new ColorRange( + new Range<>(100f, 120f), + new Range<>(0.3f, 0.7f), + new Range<>(0.424f, 0.541f)), + + // Green + new ColorRange( + new Range<>(120f, 140f), + new Range<>(0.7f, 1f), + new Range<>(0.375f, 0.52f)), + new ColorRange( + new Range<>(120f, 140f), + new Range<>(0.3f, 0.7f), + new Range<>(0.435f, 0.524f)), + + // Green Blue 140 + new ColorRange( + new Range<>(140f, 160f), + new Range<>(0.7f, 1f), + new Range<>(0.496f, 0.641f)), + + // Seafoam + new ColorRange( + new Range<>(160f, 180f), + new Range<>(0.7f, 1f), + new Range<>(0.496f, 0.567f)), + + // Cyan + new ColorRange( + new Range<>(180f, 200f), + new Range<>(0.7f, 1f), + new Range<>(0.52f, 0.729f)), + + // Blue + new ColorRange( + new Range<>(220f, 240f), + new Range<>(0.7f, 1f), + new Range<>(0.396f, 0.571f)), + new ColorRange( + new Range<>(220f, 240f), + new Range<>(0.3f, 0.7f), + new Range<>(0.425f, 0.551f)), + + // Blue Purple 240 + new ColorRange( + new Range<>(240f, 260f), + new Range<>(0.7f, 1f), + new Range<>(0.418f, 0.639f)), + new ColorRange( + new Range<>(220f, 240f), + new Range<>(0.3f, 0.7f), + new Range<>(0.441f, 0.576f)), + + // Blue Purple 260 + new ColorRange( + new Range<>(260f, 280f), + new Range<>(0.3f, 1f), // Bigger range + new Range<>(0.461f, 0.553f)), + + // Fuchsia + new ColorRange( + new Range<>(300f, 320f), + new Range<>(0.7f, 1f), + new Range<>(0.484f, 0.588f)), + new ColorRange( + new Range<>(300f, 320f), + new Range<>(0.3f, 0.7f), + new Range<>(0.48f, 0.592f)), + + // Pink + new ColorRange( + new Range<>(320f, 340f), + new Range<>(0.7f, 1f), + new Range<>(0.466f, 0.629f)), + + // Soft red + new ColorRange( + new Range<>(340f, 360f), + new Range<>(0.7f, 1f), + new Range<>(0.437f, 0.596f)) + }; + + /** + * Representation of an HSL color range. + * <ul> + * <li>hsl[0] is Hue [0 .. 360)</li> + * <li>hsl[1] is Saturation [0...1]</li> + * <li>hsl[2] is Lightness [0...1]</li> + * </ul> + */ + static class ColorRange { + private Range<Float> mHue; + private Range<Float> mSaturation; + private Range<Float> mLightness; + + ColorRange(Range<Float> hue, Range<Float> saturation, Range<Float> lightness) { + mHue = hue; + mSaturation = saturation; + mLightness = lightness; + } + + boolean containsColor(float h, float s, float l) { + if (!mHue.contains(h)) { + return false; + } else if (!mSaturation.contains(s)) { + return false; + } else if (!mLightness.contains(l)) { + return false; + } + return true; + } + + float[] getCenter() { + return new float[] { + mHue.getLower() + (mHue.getUpper() - mHue.getLower()) / 2f, + mSaturation.getLower() + (mSaturation.getUpper() - mSaturation.getLower()) / 2f, + mLightness.getLower() + (mLightness.getUpper() - mLightness.getLower()) / 2f + }; + } + + @Override + public String toString() { + return String.format("H: %s, S: %s, L %s", mHue, mSaturation, mLightness); + } + } + +} diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionService.java b/src/com/android/launcher3/dynamicui/ColorExtractionService.java index a2e118e26..b9dd3b588 100644 --- a/src/com/android/launcher3/dynamicui/ColorExtractionService.java +++ b/src/com/android/launcher3/dynamicui/ColorExtractionService.java @@ -21,6 +21,7 @@ import android.app.WallpaperManager; import android.app.job.JobParameters; import android.app.job.JobService; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; @@ -82,6 +83,10 @@ public class ColorExtractionService extends JobService { if (wallpaperManager.getWallpaperInfo() != null) { // We can't extract colors from live wallpapers; always use the default color. extractedColors.updateHotseatPalette(null); + + if (FeatureFlags.QSB_IN_HOTSEAT || FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { + extractedColors.updateWallpaperThemePalette(null); + } } else { // We extract colors for the hotseat and status bar separately, // since they only consider part of the wallpaper. @@ -90,6 +95,10 @@ public class ColorExtractionService extends JobService { if (FeatureFlags.LIGHT_STATUS_BAR) { extractedColors.updateStatusBarPalette(getStatusBarPalette()); } + + if (FeatureFlags.QSB_IN_HOTSEAT || FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { + extractedColors.updateWallpaperThemePalette(getWallpaperPalette()); + } } // Save the extracted colors and wallpaper id to LauncherProvider. @@ -173,4 +182,23 @@ public class ColorExtractionService extends JobService { .clearFilters() .generate(); } + + @TargetApi(Build.VERSION_CODES.N) + private Palette getWallpaperPalette() { + WallpaperManager wallpaperManager = WallpaperManager.getInstance(this); + if (Utilities.ATLEAST_NOUGAT) { + try (ParcelFileDescriptor fd = wallpaperManager + .getWallpaperFile(WallpaperManager.FLAG_SYSTEM)) { + Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor()); + if (bitmap != null) { + return Palette.from(bitmap).clearFilters().generate(); + } + } catch (IOException | NullPointerException e) { + Log.e(TAG, "Fetching partial bitmap failed, trying old method", e); + } + } + + Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap(); + return Palette.from(wallpaper).clearFilters().generate(); + } } diff --git a/src/com/android/launcher3/dynamicui/ExtractedColors.java b/src/com/android/launcher3/dynamicui/ExtractedColors.java index 711508ea5..2d8bb869f 100644 --- a/src/com/android/launcher3/dynamicui/ExtractedColors.java +++ b/src/com/android/launcher3/dynamicui/ExtractedColors.java @@ -16,13 +16,19 @@ package com.android.launcher3.dynamicui; +import android.app.WallpaperManager; import android.content.Context; import android.graphics.Color; +import android.support.annotation.Nullable; import android.support.v4.graphics.ColorUtils; import android.support.v7.graphics.Palette; import android.util.Log; import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; + +import java.util.ArrayList; +import java.util.Arrays; /** * Saves and loads colors extracted from the wallpaper, as well as the associated wallpaper id. @@ -32,31 +38,56 @@ public class ExtractedColors { public static final int DEFAULT_LIGHT = Color.WHITE; public static final int DEFAULT_DARK = Color.BLACK; - public static final int DEFAULT_COLOR = DEFAULT_LIGHT; // These color profile indices should NOT be changed, since they are used when saving and // loading extracted colors. New colors should always be added at the end. public static final int VERSION_INDEX = 0; public static final int HOTSEAT_INDEX = 1; public static final int STATUS_BAR_INDEX = 2; - // public static final int VIBRANT_INDEX = 2; - // public static final int VIBRANT_DARK_INDEX = 3; - // public static final int VIBRANT_LIGHT_INDEX = 4; - // public static final int MUTED_INDEX = 5; - // public static final int MUTED_DARK_INDEX = 6; - // public static final int MUTED_LIGHT_INDEX = 7; - - public static final int NUM_COLOR_PROFILES = 2; - private static final int VERSION = 1; + public static final int WALLPAPER_VIBRANT_INDEX = 3; + public static final int ALLAPPS_GRADIENT_MAIN_INDEX = 4; + public static final int ALLAPPS_GRADIENT_SECONDARY_INDEX = 5; + + private static final int VERSION; + private static final int[] DEFAULT_VALUES; + + static { + if (FeatureFlags.LAUNCHER3_GRADIENT_ALL_APPS) { + VERSION = 3; + DEFAULT_VALUES = new int[] { + VERSION, // VERSION_INDEX + 0x40FFFFFF, // HOTSEAT_INDEX: White with 25% alpha + DEFAULT_DARK, // STATUS_BAR_INDEX + 0xFFCCCCCC, // WALLPAPER_VIBRANT_INDEX + 0xFF000000, // ALLAPPS_GRADIENT_MAIN_INDEX + 0xFF000000 // ALLAPPS_GRADIENT_SECONDARY_INDEX + }; + } else if (FeatureFlags.QSB_IN_HOTSEAT) { + VERSION = 2; + DEFAULT_VALUES = new int[] { + VERSION, // VERSION_INDEX + 0x40FFFFFF, // HOTSEAT_INDEX: White with 25% alpha + DEFAULT_DARK, // STATUS_BAR_INDEX + 0xFFCCCCCC, // WALLPAPER_VIBRANT_INDEX + }; + } else { + VERSION = 1; + DEFAULT_VALUES = new int[] { + VERSION, // VERSION_INDEX + 0x40FFFFFF, // HOTSEAT_INDEX: White with 25% alpha + DEFAULT_DARK, // STATUS_BAR_INDEX + }; + } + } private static final String COLOR_SEPARATOR = ","; - private int[] mColors; + private final ArrayList<OnChangeListener> mListeners = new ArrayList<>(); + private final int[] mColors; public ExtractedColors() { // The first entry is reserved for the version number. - mColors = new int[NUM_COLOR_PROFILES + 1]; - mColors[VERSION_INDEX] = VERSION; + mColors = Arrays.copyOf(DEFAULT_VALUES, DEFAULT_VALUES.length); } public void setColorAtIndex(int index, int color) { @@ -79,17 +110,6 @@ public class ExtractedColors { } /** - * Decodes a comma-separated String into {@link #mColors}. - */ - void decodeFromString(String colorsString) { - String[] splitColorsString = colorsString.split(COLOR_SEPARATOR); - mColors = new int[splitColorsString.length]; - for (int i = 0; i < mColors.length; i++) { - mColors[i] = Integer.parseInt(splitColorsString[i]); - } - } - - /** * Loads colors and wallpaper id from {@link Utilities#getPrefs(Context)}. * These were saved there in {@link ColorExtractionService}. */ @@ -97,19 +117,22 @@ public class ExtractedColors { String encodedString = Utilities.getPrefs(context).getString( ExtractionUtils.EXTRACTED_COLORS_PREFERENCE_KEY, VERSION + ""); - decodeFromString(encodedString); - - if (mColors[VERSION_INDEX] != VERSION) { + String[] splitColorsString = encodedString.split(COLOR_SEPARATOR); + if (splitColorsString.length == DEFAULT_VALUES.length && + Integer.parseInt(splitColorsString[VERSION_INDEX]) == VERSION) { + // Parse and apply the saved values. + for (int i = 0; i < mColors.length; i++) { + mColors[i] = Integer.parseInt(splitColorsString[i]); + } + } else { + // Leave the values as default values as the saved values may not be compatible. ExtractionUtils.startColorExtractionService(context); } } /** @param index must be one of the index values defined at the top of this class. */ - public int getColor(int index, int defaultColor) { - if (index > VERSION_INDEX && index < mColors.length) { - return mColors[index]; - } - return defaultColor; + public int getColor(int index) { + return mColors[index]; } /** @@ -125,7 +148,7 @@ public class ExtractedColors { } else if (hotseatPalette != null && ExtractionUtils.isSuperDark(hotseatPalette)) { hotseatColor = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.18f * 255)); } else { - hotseatColor = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.25f * 255)); + hotseatColor = DEFAULT_VALUES[HOTSEAT_INDEX]; } setColorAtIndex(HOTSEAT_INDEX, hotseatColor); } @@ -134,4 +157,28 @@ public class ExtractedColors { setColorAtIndex(STATUS_BAR_INDEX, ExtractionUtils.isSuperLight(statusBarPalette) ? DEFAULT_LIGHT : DEFAULT_DARK); } + + public void updateWallpaperThemePalette(@Nullable Palette wallpaperPalette) { + int defaultColor = DEFAULT_VALUES[WALLPAPER_VIBRANT_INDEX]; + setColorAtIndex(WALLPAPER_VIBRANT_INDEX, wallpaperPalette == null + ? defaultColor : wallpaperPalette.getVibrantColor(defaultColor)); + } + + public void addOnChangeListener(OnChangeListener listener) { + mListeners.add(listener); + } + + public void notifyChange() { + for (OnChangeListener listener : mListeners) { + listener.onExtractedColorsChanged(); + } + } + + /** + * Interface for listening for extracted color changes + */ + public interface OnChangeListener { + + void onExtractedColorsChanged(); + } } diff --git a/src/com/android/launcher3/dynamicui/WallpaperColorInfo.java b/src/com/android/launcher3/dynamicui/WallpaperColorInfo.java new file mode 100644 index 000000000..e23f42f46 --- /dev/null +++ b/src/com/android/launcher3/dynamicui/WallpaperColorInfo.java @@ -0,0 +1,118 @@ +package com.android.launcher3.dynamicui; + +import android.content.Context; +import android.graphics.Color; +import android.support.v4.graphics.ColorUtils; +import android.util.Pair; + +import com.android.launcher3.compat.WallpaperColorsCompat; +import com.android.launcher3.compat.WallpaperManagerCompat; + +import java.util.ArrayList; + +import static android.app.WallpaperManager.FLAG_SYSTEM; + +public class WallpaperColorInfo implements WallpaperManagerCompat.OnColorsChangedListenerCompat { + + private static final int FALLBACK_COLOR = Color.WHITE; + private static final Object sInstanceLock = new Object(); + private static WallpaperColorInfo sInstance; + + public static WallpaperColorInfo getInstance(Context context) { + synchronized (sInstanceLock) { + if (sInstance == null) { + sInstance = new WallpaperColorInfo(context.getApplicationContext()); + } + return sInstance; + } + } + + private final ArrayList<OnChangeListener> mListeners = new ArrayList<>(); + private final WallpaperManagerCompat mWallpaperManager; + private final ColorExtractionAlgorithm mExtractionType; + private int mMainColor; + private int mSecondaryColor; + private boolean mIsDark; + private boolean mSupportsDarkText; + private OnThemeChangeListener mOnThemeChangeListener; + + private WallpaperColorInfo(Context context) { + mWallpaperManager = WallpaperManagerCompat.getInstance(context); + mWallpaperManager.addOnColorsChangedListener(this); + mExtractionType = ColorExtractionAlgorithm.newInstance(context); + update(mWallpaperManager.getWallpaperColors(FLAG_SYSTEM)); + } + + public int getMainColor() { + return mMainColor; + } + + public int getSecondaryColor() { + return mSecondaryColor; + } + + public boolean isDark() { + return mIsDark; + } + + public boolean supportsDarkText() { + return mSupportsDarkText; + } + + @Override + public void onColorsChanged(WallpaperColorsCompat colors, int which) { + if (which == FLAG_SYSTEM) { + boolean wasDarkTheme = mIsDark; + boolean didSupportDarkText = mSupportsDarkText; + update(colors); + notifyChange(wasDarkTheme != mIsDark || didSupportDarkText != mSupportsDarkText); + } + } + + private void update(WallpaperColorsCompat wallpaperColors) { + Pair<Integer, Integer> colors = mExtractionType.extractInto(wallpaperColors); + if (colors != null) { + mMainColor = colors.first; + mSecondaryColor = colors.second; + } else { + mMainColor = FALLBACK_COLOR; + mSecondaryColor = FALLBACK_COLOR; + } + mSupportsDarkText = wallpaperColors != null ? wallpaperColors.supportsDarkText() : false; + float[] hsl = new float[3]; + ColorUtils.colorToHSL(mMainColor, hsl); + mIsDark = hsl[2] < 0.2f; + } + + public void setOnThemeChangeListener(OnThemeChangeListener onThemeChangeListener) { + this.mOnThemeChangeListener = onThemeChangeListener; + } + + public void addOnChangeListener(OnChangeListener listener) { + mListeners.add(listener); + } + + public void removeOnChangeListener(OnChangeListener listener) { + mListeners.remove(listener); + } + + public void notifyChange(boolean themeChanged) { + if (themeChanged) { + if (mOnThemeChangeListener != null) { + mOnThemeChangeListener.onThemeChanged(); + } + } else { + for (OnChangeListener listener : mListeners) { + listener.onExtractedColorsChanged(this); + } + } + } + + public interface OnChangeListener { + void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo); + } + + public interface OnThemeChangeListener { + void onThemeChanged(); + } +} diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java index 840fcf5fe..0df787ac3 100644 --- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java +++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java @@ -1,12 +1,5 @@ package com.android.launcher3.folder; -import android.view.View; - -import com.android.launcher3.config.FeatureFlags; - -import java.util.ArrayList; -import java.util.List; - public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule { static final int MAX_NUM_ITEMS_IN_PREVIEW = 4; @@ -121,6 +114,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 +127,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 93c9ea8e3..80338ca72 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -34,7 +34,6 @@ import android.view.FocusFinder; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; -import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; @@ -46,6 +45,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; @@ -60,14 +60,16 @@ import com.android.launcher3.LauncherSettings; import com.android.launcher3.LogDecelerateInterpolator; import com.android.launcher3.OnAlarmListener; import com.android.launcher3.PagedView; +import com.android.launcher3.PendingAddItemInfo; import com.android.launcher3.R; import com.android.launcher3.ShortcutInfo; 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.anim.CircleRevealOutlineProvider; 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; @@ -75,12 +77,13 @@ import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.pageindicators.PageIndicatorDots; 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.util.Thunk; +import com.android.launcher3.widget.PendingAddShortcutInfo; 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. @@ -133,8 +136,10 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC @Thunk final ArrayList<View> mItemsInReadingOrder = new ArrayList<View>(); + private AnimatorSet mCurrentAnimator; + private final int mExpandDuration; - private final int mMaterialExpandDuration; + public final int mMaterialExpandDuration; private final int mMaterialExpandStagger; protected final Launcher mLauncher; @@ -501,51 +506,31 @@ 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); + private void startAnimation(final AnimatorSet a) { + if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) { + mCurrentAnimator.cancel(); } - - 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()); + a.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mState = STATE_ANIMATING; + mCurrentAnimator = a; } - } - - 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; + @Override + public void onAnimationEnd(Animator animation) { + mCurrentAnimator = null; + } + }); + a.start(); + } - final Runnable onCompleteRunnable; + private AnimatorSet getOpeningAnimator() { prepareReveal(); - centerAboutIcon(); - mFolderIcon.growAndFadeOut(); AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); + int width = getFolderWidth(); int height = getFolderHeight(); @@ -587,24 +572,76 @@ 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 + ? new FolderAnimationManager(this, true /* isOpening */).getAnimator() + : getOpeningAnimator(); onCompleteRunnable = new Runnable() { @Override public void run() { - mContent.setLayerType(LAYER_TYPE_NONE, null); - mFooter.setLayerType(LAYER_TYPE_NONE, null); mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); } }; anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { + if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) { + mFolderIcon.drawLeaveBehindIfExists(); + mFolderIcon.setVisibility(INVISIBLE); + } + Utilities.sendCustomAccessibilityEvent( Folder.this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, mContent.getAccessibilityDescription()); - mState = STATE_ANIMATING; } @Override public void onAnimationEnd(Animator animation) { @@ -650,7 +687,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } mPageIndicator.stopAllAnimations(); - anim.start(); + startAnimation(anim); // Make sure the folder picks up the last drag move even if the finger doesn't move. if (mDragController.isDragging()) { @@ -689,7 +726,11 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } if (mFolderIcon != null) { - mFolderIcon.shrinkAndFadeIn(animate); + if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) { + mFolderIcon.clearLeaveBehindIfExists(); + } else { + mFolderIcon.shrinkAndFadeIn(animate); + } } if (!(getParent() instanceof DragLayer)) return; @@ -706,12 +747,24 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC parent.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } + private AnimatorSet getClosingAnimator() { + 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 + ? new FolderAnimationManager(this, false /* isOpening */).getAnimator() + : getClosingAnimator(); + a.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - setLayerType(LAYER_TYPE_NONE, null); closeComplete(true); } @Override @@ -720,12 +773,9 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC Folder.this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, getContext().getString(R.string.folder_closed)); - mState = STATE_ANIMATING; } }); - oa.setDuration(mExpandDuration); - setLayerType(LAYER_TYPE_HARDWARE, null); - oa.start(); + startAnimation(a); } private void closeComplete(boolean wasAnimated) { @@ -736,8 +786,12 @@ 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.mBackground.animateBackgroundStroke(); + mFolderIcon.requestFocus(); + } } if (mRearrangeOnClose) { @@ -1027,6 +1081,9 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } public boolean isDropEnabled() { + if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) { + return mState != STATE_ANIMATING; + } return true; } @@ -1288,53 +1345,68 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } mContent.completePendingPageChanges(); - View currentDragView; - final ShortcutInfo si; - if (d.dragInfo instanceof AppInfo) { - // Came from all apps -- make a copy. - si = ((AppInfo) d.dragInfo).makeShortcut(); + if (d.dragInfo instanceof PendingAddShortcutInfo) { + PendingAddShortcutInfo pasi = (PendingAddShortcutInfo) d.dragInfo; + pasi.container = mInfo.id; + pasi.rank = mEmptyCellRank; + + mLauncher.addPendingItem(pasi, pasi.container, pasi.screenId, null, pasi.spanX, + pasi.spanY); + d.deferDragViewCleanupPostAnimation = false; + mRearrangeOnClose = true; } else { - // ShortcutInfo - si = (ShortcutInfo) d.dragInfo; - } - if (mIsExternalDrag) { - currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank); - // Actually move the item in the database if it was an external drag. Call this - // before creating the view, so that ShortcutInfo is updated appropriately. - mLauncher.getModelWriter().addOrMoveItemInDatabase( - si, mInfo.id, 0, si.cellX, si.cellY); - - // We only need to update the locations if it doesn't get handled in #onDropCompleted. - if (d.dragSource != this) { - updateItemLocationsInDatabaseBatch(); + final ShortcutInfo si; + if (d.dragInfo instanceof AppInfo) { + // Came from all apps -- make a copy. + si = ((AppInfo) d.dragInfo).makeShortcut(); + } else { + // ShortcutInfo + si = (ShortcutInfo) d.dragInfo; } - mIsExternalDrag = false; - } else { - currentDragView = mCurrentDragView; - mContent.addViewForRank(currentDragView, si, mEmptyCellRank); - } - if (d.dragView.hasDrawn()) { + View currentDragView; + if (mIsExternalDrag) { + currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank); - // Temporarily reset the scale such that the animation target gets calculated correctly. - float scaleX = getScaleX(); - float scaleY = getScaleY(); - setScaleX(1.0f); - setScaleY(1.0f); - mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView, - cleanUpRunnable, null); - setScaleX(scaleX); - setScaleY(scaleY); - } else { - d.deferDragViewCleanupPostAnimation = false; - currentDragView.setVisibility(VISIBLE); - } - mItemsInvalidated = true; - rearrangeChildren(); + // Actually move the item in the database if it was an external drag. Call this + // before creating the view, so that ShortcutInfo is updated appropriately. + mLauncher.getModelWriter().addOrMoveItemInDatabase( + si, mInfo.id, 0, si.cellX, si.cellY); + + // We only need to update the locations if it doesn't get handled in + // #onDropCompleted. + if (d.dragSource != this) { + updateItemLocationsInDatabaseBatch(); + } + mIsExternalDrag = false; + } else { + currentDragView = mCurrentDragView; + mContent.addViewForRank(currentDragView, si, mEmptyCellRank); + } + + if (d.dragView.hasDrawn()) { + // Temporarily reset the scale such that the animation target gets calculated + // correctly. + float scaleX = getScaleX(); + float scaleY = getScaleY(); + setScaleX(1.0f); + setScaleY(1.0f); + mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView, + cleanUpRunnable, null); + setScaleX(scaleX); + setScaleY(scaleY); + } else { + d.deferDragViewCleanupPostAnimation = false; + currentDragView.setVisibility(VISIBLE); + } + + mItemsInvalidated = true; + rearrangeChildren(); - // Temporarily suppress the listener, as we did all the work already here. - try (SuppressInfoChanges s = new SuppressInfoChanges()) { - mInfo.add(si, false); + // Temporarily suppress the listener, as we did all the work already here. + try (SuppressInfoChanges s = new SuppressInfoChanges()) { + mInfo.add(si, false); + } } // Clear the drag info, as it is no longer being dragged. @@ -1363,11 +1435,15 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } @Override - public void onAdd(ShortcutInfo item) { - mContent.createAndAddViewForRank(item, mContent.allocateRankForNewItem()); + public void onAdd(ShortcutInfo item, int rank) { + View view = mContent.createAndAddViewForRank(item, rank); + mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX, + item.cellY); + + ArrayList<View> items = new ArrayList<>(getItemsInReadingOrder()); + items.add(rank, view); + mContent.arrangeChildren(items, items.size()); mItemsInvalidated = true; - mLauncher.getModelWriter().addOrMoveItemInDatabase( - item, mInfo.id, 0, item.cellX, item.cellY); } public void onRemove(ShortcutInfo item) { @@ -1427,6 +1503,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..bee0bd418 --- /dev/null +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -0,0 +1,362 @@ +/* + * 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 final boolean mIsOpening; + + private final int mDuration; + private final int mDelay; + + private final TimeInterpolator mFolderInterpolator; + private final TimeInterpolator mLargeFolderPreviewItemInterpolator; + + 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, boolean isOpening) { + mFolder = folder; + mContent = folder.mContent; + mFolderBackground = (GradientDrawable) mFolder.getBackground(); + + mFolderIcon = folder.mFolderIcon; + mPreviewBackground = mFolderIcon.mBackground; + + mContext = folder.getContext(); + mLauncher = folder.mLauncher; + + mIsOpening = isOpening; + + mDuration = mFolder.mMaterialExpandDuration; + mDelay = mContext.getResources().getInteger(R.integer.config_folderDelay); + + mFolderInterpolator = AnimationUtils.loadInterpolator(mContext, + R.interpolator.folder_interpolator); + mLargeFolderPreviewItemInterpolator = AnimationUtils.loadInterpolator(mContext, + R.interpolator.large_folder_preview_item_interpolator); + } + + + /** + * Prepares the Folder for animating between open / closed states. + */ + public AnimatorSet getAnimator() { + final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams(); + FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule(); + final List<BubbleTextView> itemsInPreview = mFolderIcon.getItemsToDisplay(); + + // Match position of the FolderIcon + final Rect folderIconPos = new Rect(); + float scaleRelativeToDragLayer = mLauncher.getDragLayer() + .getDescendantRectRelativeToSelf(mFolderIcon, folderIconPos); + float initialSize = (mFolderIcon.mBackground.getRadius() * 2) * scaleRelativeToDragLayer; + + // Match size/scale of icons in the preview + float previewScale = rule.scaleForItem(0, itemsInPreview.size()); + float previewSize = rule.getIconSize() * previewScale; + float initialScale = previewSize / itemsInPreview.get(0).getIconSize() + * scaleRelativeToDragLayer; + final float finalScale = 1f; + float scale = mIsOpening ? initialScale : finalScale; + mFolder.setScaleX(scale); + mFolder.setScaleY(scale); + mFolder.setPivotX(0); + mFolder.setPivotY(0); + + // 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. + int previewItemOffsetX = (int) (previewSize / 2); + if (Utilities.isRtl(mContext.getResources())) { + previewItemOffsetX = (int) (lp.width * initialScale - initialSize - previewItemOffsetX); + } + + final int paddingOffsetX = (int) ((mFolder.getPaddingLeft() + mContent.getPaddingLeft()) + * initialScale); + final int paddingOffsetY = (int) ((mFolder.getPaddingTop() + mContent.getPaddingTop()) + * initialScale); + + // Background can have a scaled radius in drag and drop mode. + int radiusDiff = mFolderIcon.mBackground.getScaledRadius() + - mFolderIcon.mBackground.getRadius(); + + int initialX = folderIconPos.left + mFolderIcon.mBackground.getOffsetX() - paddingOffsetX + - previewItemOffsetX + radiusDiff; + int initialY = folderIconPos.top + mFolderIcon.mBackground.getOffsetY() - paddingOffsetY + + radiusDiff; + 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(mIsOpening ? initialColor : finalColor); + + // Initialize the Folder items' text. + final List<BubbleTextView> items = mFolder.getItemsOnCurrentPage(); + final int finalTextColor = Themes.getAttrColor(mContext, android.R.attr.textColorSecondary); + ITEMS_TEXT_COLOR_PROPERTY.set(items, mIsOpening ? Color.TRANSPARENT + : finalTextColor); + + // Set up the reveal animation that clips the Folder. + int totalOffsetX = paddingOffsetX + previewItemOffsetX; + Rect startRect = new Rect( + Math.round(totalOffsetX / initialScale), + Math.round(paddingOffsetY / initialScale), + Math.round((totalOffsetX + initialSize) / initialScale), + Math.round((paddingOffsetY + initialSize) / initialScale)); + Rect endRect = new Rect(0, 0, lp.width, lp.height); + float initialRadius = initialSize / initialScale / 2f; + float finalRadius = Utilities.pxFromDp(2, mContext.getResources().getDisplayMetrics()); + + // Create the animators. + AnimatorSet a = LauncherAnimUtils.createAnimatorSet(); + + play(a, getAnimator(mFolder, View.TRANSLATION_X, xDistance, 0f)); + play(a, getAnimator(mFolder, View.TRANSLATION_Y, yDistance, 0f)); + play(a, getAnimator(mFolder, SCALE_PROPERTY, initialScale, finalScale)); + play(a, getAnimator(items, ITEMS_TEXT_COLOR_PROPERTY, Color.TRANSPARENT, finalTextColor)); + play(a, getAnimator(mFolderBackground, "color", initialColor, finalColor)); + play(a, new RoundedRectRevealOutlineProvider(initialRadius, finalRadius, startRect, + endRect).createRevealAnimator(mFolder, !mIsOpening)); + + a.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + ITEMS_TEXT_COLOR_PROPERTY.set(items, finalTextColor); + mFolder.setTranslationX(0.0f); + mFolder.setTranslationY(0.0f); + mFolder.setScaleX(1f); + mFolder.setScaleY(1f); + } + }); + + // 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(mFolderInterpolator); + } + + addPreviewItemAnimators(a, initialScale / scaleRelativeToDragLayer, previewItemOffsetX); + return a; + } + + /** + * Animate the items that are displayed in the preview. + */ + private void addPreviewItemAnimators(AnimatorSet animatorSet, final float folderScale, + int previewItemOffsetX) { + FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule(); + final List<BubbleTextView> itemsInPreview = mFolderIcon.getItemsToDisplay(); + final int numItemsInPreview = itemsInPreview.size(); + + TimeInterpolator previewItemInterpolator = getPreviewItemInterpolator(); + + 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 = mIsOpening ? 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 + previewItemOffsetX) / folderScale); + final int previewPosY = (int) (mTmpParams.transY / folderScale); + + final float xDistance = previewPosX - btvLp.x; + final float yDistance = previewPosY - btvLp.y; + + Animator translationX = getAnimator(btv, View.TRANSLATION_X, xDistance, 0f); + translationX.setInterpolator(previewItemInterpolator); + play(animatorSet, translationX); + + Animator translationY = getAnimator(btv, View.TRANSLATION_Y, yDistance, 0f); + translationY.setInterpolator(previewItemInterpolator); + play(animatorSet, translationY); + + Animator scaleAnimator = getAnimator(btv, SCALE_PROPERTY, initialScale, finalScale); + scaleAnimator.setInterpolator(previewItemInterpolator); + play(animatorSet, scaleAnimator); + + if (mFolder.getItemCount() > FolderIcon.NUM_ITEMS_IN_PREVIEW) { + // These delays allows the preview items to move as part of the Folder's motion, + // and its only necessary for large folders because of differing interpolators. + if (mIsOpening) { + translationX.setStartDelay(mDelay); + translationY.setStartDelay(mDelay); + scaleAnimator.setStartDelay(mDelay); + } + translationX.setDuration(translationX.getDuration() - mDelay); + translationY.setDuration(translationY.getDuration() - mDelay); + scaleAnimator.setDuration(scaleAnimator.getDuration() - mDelay); + } + + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + // Necessary to initialize values here because of the start delay. + if (mIsOpening) { + btv.setTranslationX(xDistance); + btv.setTranslationY(yDistance); + btv.setScaleX(initialScale); + btv.setScaleY(initialScale); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + btv.setTranslationX(0.0f); + btv.setTranslationY(0.0f); + btv.setScaleX(1f); + btv.setScaleY(1f); + } + }); + } + } + + private void play(AnimatorSet as, Animator a) { + a.setDuration(mDuration); + as.play(a); + } + + private TimeInterpolator getPreviewItemInterpolator() { + 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 mLargeFolderPreviewItemInterpolator; + } + return mFolderInterpolator; + } + + private Animator getAnimator(View view, Property property, float v1, float v2) { + return mIsOpening + ? ObjectAnimator.ofFloat(view, property, v1, v2) + : ObjectAnimator.ofFloat(view, property, v2, v1); + } + + private Animator getAnimator(List<BubbleTextView> items, Property property, int v1, int v2) { + return mIsOpening + ? ObjectAnimator.ofArgb(items, property, v1, v2) + : ObjectAnimator.ofArgb(items, property, v2, v1); + } + + private Animator getAnimator(GradientDrawable drawable, String property, int v1, int v2) { + return mIsOpening + ? ObjectAnimator.ofArgb(drawable, property, v1, v2) + : ObjectAnimator.ofArgb(drawable, property, v2, v1); + } +} diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 8d58fef36..b793f491e 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -36,8 +36,10 @@ import android.graphics.Region; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.os.Parcelable; +import android.support.v4.graphics.ColorUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.util.Log; import android.util.Property; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -75,8 +77,10 @@ import com.android.launcher3.badge.FolderBadgeInfo; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; +import com.android.launcher3.util.Themes; import com.android.launcher3.graphics.IconPalette; import com.android.launcher3.util.Thunk; +import com.android.launcher3.widget.PendingAddShortcutInfo; import java.util.ArrayList; import java.util.List; @@ -126,6 +130,7 @@ 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; @@ -181,7 +186,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { } DeviceProfile grid = launcher.getDeviceProfile(); - FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false); + FolderIcon icon = (FolderIcon) LayoutInflater.from(group.getContext()) + .inflate(resId, group, false); icon.setClipToPadding(false); icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name); @@ -221,6 +227,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private void setFolder(Folder folder) { mFolder = folder; + mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv); updateItemDrawingParams(false); } @@ -249,11 +256,9 @@ public class FolderIcon extends FrameLayout implements FolderListener { mBackground.animateToAccept(cl, lp.cellX, lp.cellY); mOpenAlarm.setOnAlarmListener(mOnOpenListener); if (SPRING_LOADING_ENABLED && - ((dragInfo instanceof AppInfo) || (dragInfo instanceof ShortcutInfo))) { - // TODO: we currently don't support spring-loading for PendingAddShortcutInfos even - // though widget-style shortcuts can be added to folders. The issue is that we need - // to deal with configuration activities which are currently handled in - // Workspace#onDropExternal. + ((dragInfo instanceof AppInfo) + || (dragInfo instanceof ShortcutInfo) + || (dragInfo instanceof PendingAddShortcutInfo))) { mOpenAlarm.setAlarm(ON_OPEN_DELAY); } } @@ -383,14 +388,11 @@ public class FolderIcon extends FrameLayout implements FolderListener { private void computePreviewDrawingParams(int drawableSize, int totalSize) { if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize || mPrevTopPadding != getPaddingTop()) { - DeviceProfile grid = mLauncher.getDeviceProfile(); - mIntrinsicIconSize = drawableSize; mTotalWidth = totalSize; mPrevTopPadding = getPaddingTop(); - mBackground.setup(getResources().getDisplayMetrics(), grid, this, mTotalWidth, - getPaddingTop()); + mBackground.setup(mLauncher, this, mTotalWidth, getPaddingTop()); mPreviewLayoutRule.init(mBackground.previewSize, mIntrinsicIconSize, Utilities.isRtl(getResources())); @@ -407,6 +409,10 @@ public class FolderIcon extends FrameLayout implements FolderListener { mBadgeInfo = badgeInfo; } + public PreviewLayoutRule getLayoutRule() { + return mPreviewLayoutRule; + } + /** * Sets mBadgeScale to 1 or 0, animating if wasBadged or isBadged is false * (the badge is being added or removed). @@ -539,7 +545,9 @@ public class FolderIcon extends FrameLayout implements FolderListener { private float mScale = 1f; private float mColorMultiplier = 1f; + private int mBgColor; private float mStrokeWidth; + private int mStrokeAlpha = MAX_BG_OPACITY; private View mInvalidateDelegate; public int previewSize; @@ -561,15 +569,31 @@ public class FolderIcon extends FrameLayout implements FolderListener { // Expressed on a scale from 0 to 255. 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 = 40; ValueAnimator mScaleAnimator; + ObjectAnimator mStrokeAlphaAnimator; - public void setup(DisplayMetrics dm, DeviceProfile grid, View invalidateDelegate, + private static final Property<PreviewBackground, Integer> STROKE_ALPHA = + new Property<PreviewBackground, Integer>(Integer.class, "strokeAlpha") { + @Override + public Integer get(PreviewBackground previewBackground) { + return previewBackground.mStrokeAlpha; + } + + @Override + public void set(PreviewBackground previewBackground, Integer alpha) { + previewBackground.mStrokeAlpha = alpha; + previewBackground.invalidate(); + } + }; + + public void setup(Launcher launcher, View invalidateDelegate, int availableSpace, int topPadding) { mInvalidateDelegate = invalidateDelegate; + mBgColor = Themes.getAttrColor(launcher, android.R.attr.colorPrimary); + DeviceProfile grid = launcher.getDeviceProfile(); final int previewSize = grid.folderIconSizePx; final int previewPadding = grid.folderIconPreviewPadding; @@ -579,7 +603,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { basePreviewOffsetY = previewPadding + grid.folderBackgroundOffset + topPadding; // Stroke width is 1dp - mStrokeWidth = dm.density; + mStrokeWidth = launcher.getResources().getDisplayMetrics().density; float radius = getScaledRadius(); float shadowRadius = radius + mStrokeWidth; @@ -634,7 +658,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { public void drawBackground(Canvas canvas) { mPaint.setStyle(Paint.Style.FILL); int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier); - mPaint.setColor(Color.argb(alpha, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY)); + mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, alpha)); drawCircle(canvas, 0 /* deltaRadius */); @@ -674,8 +698,24 @@ public class FolderIcon extends FrameLayout implements FolderListener { canvas.restoreToCount(saveCount); } + public void animateBackgroundStroke() { + if (mStrokeAlphaAnimator != null) { + mStrokeAlphaAnimator.cancel(); + } + mStrokeAlphaAnimator = ObjectAnimator + .ofInt(this, STROKE_ALPHA, MAX_BG_OPACITY / 2, MAX_BG_OPACITY) + .setDuration(100); + mStrokeAlphaAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mStrokeAlphaAnimator = null; + } + }); + mStrokeAlphaAnimator.start(); + } + public void drawBackgroundStroke(Canvas canvas) { - mPaint.setColor(Color.argb(255, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY)); + mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, mStrokeAlpha)); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mStrokeWidth); drawCircle(canvas, 1 /* deltaRadius */); @@ -822,6 +862,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) { @@ -992,8 +1040,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(); @@ -1008,7 +1074,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); @@ -1044,7 +1110,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { } @Override - public void onAdd(ShortcutInfo item) { + public void onAdd(ShortcutInfo item, int rank) { boolean wasBadged = mBadgeInfo.hasBadge(); mBadgeInfo.addBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item)); boolean isBadged = mBadgeInfo.hasBadge(); @@ -1110,28 +1176,21 @@ public class FolderIcon extends FrameLayout implements FolderListener { } public void shrinkAndFadeIn(boolean animate) { - final CellLayout cl = (CellLayout) getParent().getParent(); - ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true; - // We remove and re-draw the FolderIcon in-case it has changed final PreviewImageView previewImage = PreviewImageView.get(getContext()); previewImage.removeFromParent(); copyToPreview(previewImage); - if (cl != null) { - cl.clearFolderLeaveBehind(); - } + clearLeaveBehindIfExists(); ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(previewImage, 1, 1, 1); oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); oa.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - if (cl != null) { - // Remove the ImageView copy of the FolderIcon and make the original visible. - previewImage.removeFromParent(); - setVisibility(View.VISIBLE); - } + // Remove the ImageView copy of the FolderIcon and make the original visible. + previewImage.removeFromParent(); + setVisibility(View.VISIBLE); } }); oa.start(); @@ -1140,7 +1199,15 @@ public class FolderIcon extends FrameLayout implements FolderListener { } } - public void growAndFadeOut() { + public void clearLeaveBehindIfExists() { + ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true; + if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + CellLayout cl = (CellLayout) getParent().getParent(); + cl.clearFolderLeaveBehind(); + } + } + + public void drawLeaveBehindIfExists() { CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); // While the folder is open, the position of the icon cannot change. lp.canReorder = false; @@ -1148,6 +1215,10 @@ public class FolderIcon extends FrameLayout implements FolderListener { CellLayout cl = (CellLayout) getParent().getParent(); cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY); } + } + + public void growAndFadeOut() { + drawLeaveBehindIfExists(); // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original PreviewImageView previewImage = PreviewImageView.get(getContext()); @@ -1177,8 +1248,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 eecce183a..19a16b18a 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -35,17 +35,14 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel; import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace.ItemOperator; -import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.pageindicators.PageIndicator; -import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; import java.util.ArrayList; @@ -68,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; @@ -108,7 +105,6 @@ public class FolderPagedView extends PagedView { mIsRtl = Utilities.isRtl(getResources()); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - setEdgeGlowColor(Themes.getAttrColor(context, android.R.attr.colorEdgeEffect)); mFocusIndicatorHelper = new ViewGroupFocusHelper(this); } @@ -120,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} & {@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); @@ -185,21 +199,26 @@ public class FolderPagedView extends PagedView { return extra; } + public void allocateSpaceForRank(int rank) { + ArrayList<View> views = new ArrayList<>(mFolder.getItemsInReadingOrder()); + views.add(rank, null); + arrangeChildren(views, views.size(), false); + } + /** * Create space for a new item at the end, and returns the rank for that item. * Also sets the current page to the last page. */ public int allocateRankForNewItem() { int rank = getItemCount(); - ArrayList<View> views = new ArrayList<>(mFolder.getItemsInReadingOrder()); - views.add(rank, null); - arrangeChildren(views, views.size(), false); + allocateSpaceForRank(rank); setCurrentPage(rank / mMaxItemsPerPage); return rank; } public View createAndAddViewForRank(ShortcutInfo item, int rank) { View icon = createNewView(item); + allocateSpaceForRank(rank); addViewForRank(icon, item, rank); return icon; } @@ -314,6 +333,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; @@ -346,7 +367,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(); } } @@ -400,12 +421,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/PreviewImageView.java b/src/com/android/launcher3/folder/PreviewImageView.java index c4f3ee15c..65d9db118 100644 --- a/src/com/android/launcher3/folder/PreviewImageView.java +++ b/src/com/android/launcher3/folder/PreviewImageView.java @@ -22,7 +22,6 @@ import android.graphics.Canvas; import android.graphics.PorterDuff; import android.graphics.Rect; import android.view.View; -import android.view.ViewGroup; import android.widget.ImageView; import com.android.launcher3.Launcher; diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java index 297203ae2..12bca5fdf 100644 --- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java +++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java @@ -16,12 +16,8 @@ package com.android.launcher3.folder; -import android.view.View; - import com.android.launcher3.folder.FolderIcon.PreviewItemDrawingParams; -import java.util.List; - public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule { static final int MAX_NUM_ITEMS_IN_PREVIEW = 3; @@ -87,6 +83,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 +99,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/GradientView.java b/src/com/android/launcher3/graphics/GradientView.java new file mode 100644 index 000000000..9dd950454 --- /dev/null +++ b/src/com/android/launcher3/graphics/GradientView.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.graphics; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RadialGradient; +import android.graphics.RectF; +import android.graphics.Shader; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.Interpolator; + +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.dynamicui.WallpaperColorInfo; + +/** + * Draws a translucent radial gradient background from an initial state with progress 0.0 to a + * final state with progress 1.0; + */ +public class GradientView extends View implements WallpaperColorInfo.OnChangeListener { + + private static final int DEFAULT_COLOR = Color.WHITE; + private static final float GRADIENT_ALPHA_MASK_LENGTH_DP = 300; + private static final boolean DEBUG = false; + + private final Bitmap mFinalGradientMask; + private final Bitmap mAlphaGradientMask; + + private int mColor1 = DEFAULT_COLOR; + private int mColor2 = DEFAULT_COLOR; + private int mWidth; + private int mHeight; + private final RectF mAlphaMaskRect = new RectF(); + private final RectF mFinalMaskRect = new RectF(); + private final Paint mPaint = new Paint(); + private float mProgress; + private final int mMaskHeight; + private final Context mAppContext; + private final Paint mDebugPaint = DEBUG ? new Paint() : null; + private final Interpolator mAccelerator = new AccelerateInterpolator(); + private final float mAlphaStart; + private final WallpaperColorInfo mWallpaperColorInfo; + + public GradientView(Context context, AttributeSet attrs) { + super(context, attrs); + this.mAppContext = context.getApplicationContext(); + this.mMaskHeight = Utilities.pxFromDp(GRADIENT_ALPHA_MASK_LENGTH_DP, + mAppContext.getResources().getDisplayMetrics()); + Launcher launcher = Launcher.getLauncher(context); + this.mAlphaStart = launcher.getDeviceProfile().isVerticalBarLayout() ? 0 : 100; + this.mWallpaperColorInfo = WallpaperColorInfo.getInstance(launcher); + updateColors(); + + int finalAlpha = 0xBF; + mFinalGradientMask = Utilities.convertToAlphaMask( + Utilities.createOnePixBitmap(), finalAlpha); + Bitmap alphaMaskFromResource = BitmapFactory.decodeResource(context.getResources(), + R.drawable.all_apps_alpha_mask); + mAlphaGradientMask = Utilities.convertToAlphaMask( + alphaMaskFromResource, finalAlpha); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mWallpaperColorInfo.addOnChangeListener(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mWallpaperColorInfo.removeOnChangeListener(this); + } + + @Override + public void onExtractedColorsChanged(WallpaperColorInfo info) { + updateColors(); + invalidate(); + } + + private void updateColors() { + this.mColor1 = mWallpaperColorInfo.getMainColor(); + this.mColor2 = mWallpaperColorInfo.getSecondaryColor(); + if (mWidth + mHeight > 0) { + createRadialShader(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + this.mWidth = getMeasuredWidth(); + this.mHeight = getMeasuredHeight(); + if (mWidth + mHeight > 0) { + createRadialShader(); + } + } + + // only being called when colors change + private void createRadialShader() { + final float gradientCenterY = 1.05f; + float radius = Math.max(mHeight, mWidth) * gradientCenterY; + + float posScreenBottom = (radius - mHeight) / radius; // center lives below screen + RadialGradient shader = new RadialGradient( + mWidth * 0.5f, + mHeight * gradientCenterY, + radius, + new int[] {mColor1, mColor1, mColor2}, + new float[] {0f, posScreenBottom, 1f}, + Shader.TileMode.CLAMP); + mPaint.setShader(shader); + } + + public void setProgress(float progress) { + this.mProgress = progress; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + float head = 0.29f; + float linearProgress = head + (mProgress * (1f - head)); + float startMaskY = (1f - linearProgress) * mHeight - mMaskHeight * linearProgress; + float interpolatedAlpha = (255 - mAlphaStart) * mAccelerator.getInterpolation(mProgress); + mPaint.setAlpha((int) (mAlphaStart + interpolatedAlpha)); + mAlphaMaskRect.set(0, startMaskY, mWidth, startMaskY + mMaskHeight); + mFinalMaskRect.set(0, startMaskY + mMaskHeight, mWidth, mHeight); + canvas.drawBitmap(mAlphaGradientMask, null, mAlphaMaskRect, mPaint); + canvas.drawBitmap(mFinalGradientMask, null, mFinalMaskRect, mPaint); + + if (DEBUG) { + mDebugPaint.setColor(0xFF00FF00); + canvas.drawLine(0, startMaskY, mWidth, startMaskY, mDebugPaint); + canvas.drawLine(0, startMaskY + mMaskHeight, mWidth, startMaskY + mMaskHeight, mDebugPaint); + } + } +}
\ No newline at end of file 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 a17ceeb94..6e01ed503 100644 --- a/src/com/android/launcher3/graphics/IconPalette.java +++ b/src/com/android/launcher3/graphics/IconPalette.java @@ -169,43 +169,40 @@ public class IconPalette { * This was copied from com.android.internal.util.NotificationColorUtil. */ private static int ensureTextContrast(int color, int bg) { - return findContrastColor(color, bg, true, 4.5); + return findContrastColor(color, bg, 4.5); } /** * Finds a suitable color such that there's enough contrast. * - * @param color the color to start searching from. - * @param other the color to ensure contrast against. Assumed to be lighter than {@param color} - * @param findFg if true, we assume {@param color} is a foreground, otherwise a background. + * @param fg the color to start searching from. + * @param bg the color to ensure contrast against. * @param minRatio the minimum contrast ratio required. * @return a color with the same hue as {@param color}, potentially darkened to meet the * contrast ratio. * * This was copied from com.android.internal.util.NotificationColorUtil. */ - private static int findContrastColor(int color, int other, boolean findFg, double minRatio) { - int fg = findFg ? color : other; - int bg = findFg ? other : color; + private static int findContrastColor(int fg, int bg, double minRatio) { if (ColorUtils.calculateContrast(fg, bg) >= minRatio) { - return color; + return fg; } double[] lab = new double[3]; - ColorUtils.colorToLAB(findFg ? fg : bg, lab); + ColorUtils.colorToLAB(bg, lab); + double bgL = lab[0]; + ColorUtils.colorToLAB(fg, lab); + double fgL = lab[0]; + boolean isBgDark = bgL < 50; - double low = 0, high = lab[0]; + double low = isBgDark ? fgL : 0, high = isBgDark ? 100 : fgL; final double a = lab[1], b = lab[2]; for (int i = 0; i < 15 && high - low > 0.00001; i++) { final double l = (low + high) / 2; - if (findFg) { - fg = ColorUtils.LABToColor(l, a, b); - } else { - bg = ColorUtils.LABToColor(l, a, b); - } + fg = ColorUtils.LABToColor(l, a, b); if (ColorUtils.calculateContrast(fg, bg) > minRatio) { - low = l; + if (isBgDark) high = l; else low = l; } else { - high = l; + if (isBgDark) low = l; else high = l; } } return ColorUtils.LABToColor(low, a, b); diff --git a/src/com/android/launcher3/graphics/IconShapeOverride.java b/src/com/android/launcher3/graphics/IconShapeOverride.java index 6e4d36642..e2d1d50e4 100644 --- a/src/com/android/launcher3/graphics/IconShapeOverride.java +++ b/src/com/android/launcher3/graphics/IconShapeOverride.java @@ -15,13 +15,14 @@ */ package com.android.launcher3.graphics; +import static com.android.launcher3.Utilities.getDevicePrefs; + import android.annotation.TargetApi; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.content.res.Resources; import android.os.Build; import android.os.SystemClock; @@ -34,11 +35,10 @@ import android.text.TextUtils; import android.util.Log; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherFiles; import com.android.launcher3.LauncherModel; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.util.LooperExecuter; +import com.android.launcher3.util.LooperExecutor; import java.lang.reflect.Field; @@ -101,7 +101,7 @@ public class IconShapeOverride { } catch (Exception e) { Log.e(TAG, "Unable to override icon shape", e); // revert value. - prefs(context).edit().remove(KEY_PREFERENCE).apply(); + getDevicePrefs(context).edit().remove(KEY_PREFERENCE).apply(); } } @@ -116,11 +116,7 @@ public class IconShapeOverride { } private static String getAppliedValue(Context context) { - return prefs(context).getString(KEY_PREFERENCE, ""); - } - - private static SharedPreferences prefs(Context context) { - return context.getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, 0); + return getDevicePrefs(context).getString(KEY_PREFERENCE, ""); } public static void handlePreferenceUi(ListPreference preference) { @@ -169,7 +165,7 @@ public class IconShapeOverride { mContext.getString(R.string.icon_shape_override_progress), true /* indeterminate */, false /* cancelable */); - new LooperExecuter(LauncherModel.getWorkerLooper()).execute( + new LooperExecutor(LauncherModel.getWorkerLooper()).execute( new OverrideApplyHandler(mContext, newValue)); } return false; @@ -189,7 +185,7 @@ public class IconShapeOverride { @Override public void run() { // Synchronously write the preference. - prefs(mContext).edit().putString(KEY_PREFERENCE, mValue).commit(); + getDevicePrefs(mContext).edit().putString(KEY_PREFERENCE, mValue).commit(); // Clear the icon cache. LauncherAppState.getInstance(mContext).getIconCache().clear(); diff --git a/src/com/android/launcher3/graphics/ScrimView.java b/src/com/android/launcher3/graphics/ScrimView.java new file mode 100644 index 000000000..6d1f30a41 --- /dev/null +++ b/src/com/android/launcher3/graphics/ScrimView.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.graphics; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; +import android.support.v4.graphics.ColorUtils; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.Interpolator; + +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.util.Themes; + +public class ScrimView extends View { + + private static final boolean DEBUG = false; + + private static final int MASK_HEIGHT_DP = 300; + private static final float MASK_START_LENGTH_FACTOR = 1f; + private static final boolean APPLY_ALPHA = true; + + private final Bitmap mFinalScrimMask; + private final Bitmap mAlphaScrimMask; + + private final int mMaskHeight; + private int mVisibleHeight; + private final int mHeadStart; + + private final RectF mAlphaMaskRect = new RectF(); + private final RectF mFinalMaskRect = new RectF(); + private final Paint mPaint = new Paint(); + private float mProgress; + private final Interpolator mAccelerator = new AccelerateInterpolator(); + private final Paint mDebugPaint = DEBUG ? new Paint() : null; + private final int mAlphaStart; + + public ScrimView(Context context, AttributeSet attrs) { + super(context, attrs); + mMaskHeight = Utilities.pxFromDp(MASK_HEIGHT_DP, getResources().getDisplayMetrics()); + mHeadStart = (int) (mMaskHeight * MASK_START_LENGTH_FACTOR); + mAlphaStart = Launcher.getLauncher(context) + .getDeviceProfile().isVerticalBarLayout() ? 0 : 55; + + int scrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor); + int scrimAlpha = Color.alpha(scrimColor); + mPaint.setColor(scrimColor); + mFinalScrimMask = Utilities.convertToAlphaMask( + Utilities.createOnePixBitmap(), scrimAlpha); + Bitmap alphaMaskFromResource = BitmapFactory.decodeResource(getResources(), + R.drawable.all_apps_alpha_mask); + mAlphaScrimMask = Utilities.convertToAlphaMask(alphaMaskFromResource, scrimAlpha); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + mVisibleHeight = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(width, mVisibleHeight * 2); + setProgress(mProgress); + } + + public void setProgress(float progress) { + mProgress = progress; + float initialY = mVisibleHeight - mHeadStart; + float fullTranslationY = mVisibleHeight; + float linTranslationY = initialY - progress * fullTranslationY; + setTranslationY(linTranslationY); + + if (APPLY_ALPHA) { + int alpha = mAlphaStart + (int) ((255f - mAlphaStart) + * mAccelerator.getInterpolation(progress)); + mPaint.setAlpha(alpha); + invalidate(); + } + } + + @Override + protected void onDraw(Canvas canvas) { + mAlphaMaskRect.set(0, 0, getWidth(), mMaskHeight); + mFinalMaskRect.set(0, mMaskHeight, getWidth(), getHeight()); + canvas.drawBitmap(mAlphaScrimMask, null, mAlphaMaskRect, mPaint); + canvas.drawBitmap(mFinalScrimMask, null, mFinalMaskRect, mPaint); + + if (DEBUG) { + mDebugPaint.setColor(0xFF0000FF); + canvas.drawLine(0, mAlphaMaskRect.top, getWidth(), mAlphaMaskRect.top, mDebugPaint); + canvas.drawLine(0, mAlphaMaskRect.bottom, getWidth(), mAlphaMaskRect.bottom, mDebugPaint); + } + } + +} diff --git a/src/com/android/launcher3/graphics/ShadowDrawable.java b/src/com/android/launcher3/graphics/ShadowDrawable.java new file mode 100644 index 000000000..45c1b6afb --- /dev/null +++ b/src/com/android/launcher3/graphics/ShadowDrawable.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.graphics; + +import android.annotation.TargetApi; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BlurMaskFilter; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.AttributeSet; + +import com.android.launcher3.R; +import com.android.launcher3.Utilities; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * A drawable which adds shadow around a child drawable. + */ +@TargetApi(Build.VERSION_CODES.O) +public class ShadowDrawable extends Drawable { + + private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + + private final ShadowDrawableState mState; + + @SuppressWarnings("unused") + public ShadowDrawable() { + this(new ShadowDrawableState()); + } + + private ShadowDrawable(ShadowDrawableState state) { + mState = state; + } + + @Override + public void draw(Canvas canvas) { + Rect bounds = getBounds(); + if (bounds.isEmpty()) { + return; + } + if (mState.mLastDrawnBitmap == null) { + regenerateBitmapCache(); + } + canvas.drawBitmap(mState.mLastDrawnBitmap, null, bounds, mPaint); + } + + @Override + public void setAlpha(int alpha) { + mPaint.setAlpha(alpha); + invalidateSelf(); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mPaint.setColorFilter(colorFilter); + invalidateSelf(); + } + + @Override + public ConstantState getConstantState() { + return mState; + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public int getIntrinsicHeight() { + return mState.mIntrinsicHeight; + } + + @Override + public int getIntrinsicWidth() { + return mState.mIntrinsicWidth; + } + + @Override + public boolean canApplyTheme() { + return mState.canApplyTheme(); + } + + @Override + public void applyTheme(Resources.Theme t) { + if (mState.canApplyTheme()) { + // Workaround since ColorStateList does not expose applyTheme method + ColorDrawable cd = new ColorDrawable(); + cd.setTintList(mState.mTintColor); + cd.applyTheme(t); + + mState.mLastDrawnBitmap = null; + invalidateSelf(); + } + } + + private void regenerateBitmapCache() { + Bitmap bitmap = Bitmap.createBitmap(mState.mIntrinsicWidth, mState.mIntrinsicHeight, + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared. + Drawable d = mState.mChildState.newDrawable().mutate(); + d.setBounds(mState.mShadowSize, mState.mShadowSize, + mState.mIntrinsicWidth - mState.mShadowSize, + mState.mIntrinsicHeight - mState.mShadowSize); + if (mState.mTintColor != null) { + d.setTint(mState.mTintColor.getDefaultColor()); + } + d.draw(canvas); + + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, BlurMaskFilter.Blur.NORMAL)); + int[] offset = new int[2]; + Bitmap shadow = bitmap.extractAlpha(paint, offset); + + paint.setMaskFilter(null); + paint.setColor(mState.mShadowColor); + bitmap.eraseColor(Color.TRANSPARENT); + canvas.drawBitmap(shadow, offset[0], offset[1], paint); + d.draw(canvas); + + if (Utilities.isAtLeastO()) { + bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false); + } + mState.mLastDrawnBitmap = bitmap; + } + + @Override + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, + Resources.Theme theme) throws XmlPullParserException, IOException { + super.inflate(r, parser, attrs, theme); + + final TypedArray a = theme == null + ? r.obtainAttributes(attrs, R.styleable.ShadowDrawable) + : theme.obtainStyledAttributes(attrs, R.styleable.ShadowDrawable, 0, 0); + + try { + Drawable d = a.getDrawable(R.styleable.ShadowDrawable_android_src); + if (d == null) { + throw new XmlPullParserException("missing src attribute"); + } + mState.mShadowColor = a.getColor( + R.styleable.ShadowDrawable_android_shadowColor, Color.BLACK); + mState.mShadowSize = a.getDimensionPixelSize( + R.styleable.ShadowDrawable_android_elevation, 0); + mState.mTintColor = a.getColorStateList(R.styleable.ShadowDrawable_android_tint); + + mState.mIntrinsicHeight = d.getIntrinsicHeight() + 2 * mState.mShadowSize; + mState.mIntrinsicWidth = d.getIntrinsicWidth() + 2 * mState.mShadowSize; + mState.mChangingConfigurations = d.getChangingConfigurations(); + + mState.mChildState = d.getConstantState(); + } finally { + a.recycle(); + } + } + + private static class ShadowDrawableState extends ConstantState { + + int mChangingConfigurations; + int mIntrinsicWidth; + int mIntrinsicHeight; + + int mShadowColor; + int mShadowSize; + ColorStateList mTintColor; + + Bitmap mLastDrawnBitmap; + ConstantState mChildState; + + @Override + public Drawable newDrawable() { + return new ShadowDrawable(this); + } + + @Override + public int getChangingConfigurations() { + return mChangingConfigurations; + } + + @Override + public boolean canApplyTheme() { + return mTintColor != null; + } + } +} diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/graphics/ShadowGenerator.java index 5d8cca8d2..37fb4ecb0 100644 --- a/src/com/android/launcher3/graphics/ShadowGenerator.java +++ b/src/com/android/launcher3/graphics/ShadowGenerator.java @@ -22,8 +22,10 @@ import android.graphics.Bitmap.Config; import android.graphics.BlurMaskFilter; import android.graphics.BlurMaskFilter.Blur; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; +import android.support.v4.graphics.ColorUtils; import com.android.launcher3.LauncherAppState; import com.android.launcher3.util.Preconditions; @@ -39,9 +41,9 @@ public class ShadowGenerator { // Percent of actual icon size private static final float KEY_SHADOW_DISTANCE = 1f/48; - public static final int KEY_SHADOW_ALPHA = 61; + private static final int KEY_SHADOW_ALPHA = 61; - public static final int AMBIENT_SHADOW_ALPHA = 30; + private static final int AMBIENT_SHADOW_ALPHA = 30; private static final Object LOCK = new Object(); // Singleton object guarded by {@link #LOCK} @@ -84,43 +86,43 @@ public class ShadowGenerator { } public static Bitmap createPillWithShadow(int rectColor, int width, int height) { - float shadowRadius = height * 1f / 32; float shadowYOffset = height * 1f / 16; + return createPillWithShadow(rectColor, width, height, shadowRadius, shadowYOffset, + new RectF()); + } + public static Bitmap createPillWithShadow(int rectColor, int width, int height, + float shadowRadius, float shadowYOffset, RectF outRect) { int radius = height / 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 centerX = Math.round(width / 2 + shadowRadius); int centerY = Math.round(radius + shadowRadius + shadowYOffset); int center = Math.max(centerX, centerY); int size = center * 2; Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888); - canvas.setBitmap(result); - int left = center - width / 2; - int top = center - height / 2; - int right = center + width / 2; - int bottom = center + height / 2; + outRect.set(0, 0, width, height); + outRect.offsetTo(center - width / 2, center - height / 2); - // Draw ambient shadow, center aligned within size - blurPaint.setAlpha(AMBIENT_SHADOW_ALPHA); - canvas.drawRoundRect(left, top, right, bottom, radius, radius, blurPaint); + drawShadow(new Canvas(result), outRect, rectColor, shadowRadius, shadowYOffset, radius); + return result; + } - // Draw key shadow, bottom aligned within size - blurPaint.setAlpha(KEY_SHADOW_ALPHA); - canvas.drawRoundRect(left, top + shadowYOffset, right, bottom + shadowYOffset, - radius, radius, blurPaint); + public static void drawShadow(Canvas c, RectF bounds, int color, + float shadowBlur, float keyShadowDistance, float radius) { + Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + p.setColor(color); - // Draw the circle - Paint drawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); - drawPaint.setColor(rectColor); - canvas.drawRoundRect(left, top, right, bottom, radius, radius, drawPaint); + // Key shadow + p.setShadowLayer(shadowBlur, 0, keyShadowDistance, + ColorUtils.setAlphaComponent(Color.BLACK, KEY_SHADOW_ALPHA)); + c.drawRoundRect(bounds, radius, radius, p); - return result; + // Ambient shadow + p.setShadowLayer(shadowBlur, 0, 0, + ColorUtils.setAlphaComponent(Color.BLACK, AMBIENT_SHADOW_ALPHA)); + c.drawRoundRect(bounds, radius, radius, p); } public static ShadowGenerator getInstance(Context context) { diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java index bb0b58add..938955ca2 100644 --- a/src/com/android/launcher3/keyboard/CustomActionsPopup.java +++ b/src/com/android/launcher3/keyboard/CustomActionsPopup.java @@ -24,10 +24,10 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.PopupMenu; import android.widget.PopupMenu.OnMenuItemClickListener; -import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; +import com.android.launcher3.popup.PopupContainerWithArrow; import java.util.ArrayList; import java.util.Collections; 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 499fdc7d3..329b7d561 100644 --- a/src/com/android/launcher3/logging/LoggerUtils.java +++ b/src/com/android/launcher3/logging/LoggerUtils.java @@ -15,7 +15,6 @@ */ package com.android.launcher3.logging; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.SparseArray; import android.view.View; diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java index 258af1689..edbb88c93 100644 --- a/src/com/android/launcher3/logging/UserEventDispatcher.java +++ b/src/com/android/launcher3/logging/UserEventDispatcher.java @@ -21,6 +21,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.SystemClock; +import android.support.annotation.Nullable; import android.util.Log; import android.view.View; import android.view.ViewParent; @@ -29,7 +30,7 @@ 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.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; @@ -60,7 +61,7 @@ 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); public static UserEventDispatcher newInstance(Context context, boolean isInLandscapeMode, boolean isInMultiWindowMode) { @@ -90,7 +91,7 @@ public class UserEventDispatcher { /** * Recursively finds the parent of the given child which implements IconLogInfoProvider */ - public static LogContainerProvider getLaunchProviderRecursive(View v) { + public static LogContainerProvider getLaunchProviderRecursive(@Nullable View v) { ViewParent parent; if (v != null) { parent = v.getParent(); @@ -147,7 +148,11 @@ public class UserEventDispatcher { return event; } - public boolean fillInLogContainerData(LauncherEvent event, View v) { + /** + * Fills in the container data on the given event if the given view is not null. + * @return whether container data was added. + */ + private boolean fillInLogContainerData(LauncherEvent event, @Nullable View v) { // Fill in grid(x,y), pageIndex of the child and container type of the parent LogContainerProvider provider = getLaunchProviderRecursive(v); if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) { @@ -203,9 +208,16 @@ public class UserEventDispatcher { } public void logActionOnControl(int action, int controlType) { - LauncherEvent event = newLauncherEvent( - newTouchAction(action), newTarget(Target.Type.CONTROL)); + logActionOnControl(action, controlType, null); + } + + public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer) { + final LauncherEvent event = controlInContainer == null + ? newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL)) + : newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL), + newTarget(Target.Type.CONTAINER)); event.srcTarget[0].controlType = controlType; + fillInLogContainerData(event, controlInContainer); dispatchUserEvent(event, null); } diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java index 969605483..2e8e15bf7 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -17,6 +17,8 @@ package com.android.launcher3.model; import android.content.Context; import android.content.Intent; +import android.content.pm.LauncherActivityInfo; +import android.os.Process; import android.os.UserHandle; import android.util.LongSparseArray; import android.util.Pair; @@ -33,10 +35,13 @@ import com.android.launcher3.LauncherModel.CallbackTask; import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherSettings; import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.Utilities; import com.android.launcher3.util.GridOccupancy; +import com.android.launcher3.util.ManagedProfileHeuristic.UserFolderInfo; import com.android.launcher3.util.Provider; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; /** @@ -44,18 +49,18 @@ import java.util.List; */ public class AddWorkspaceItemsTask extends ExtendedModelTask { - private final Provider<List<ItemInfo>> mAppsProvider; + private final Provider<List<Pair<ItemInfo, Object>>> mAppsProvider; /** * @param appsProvider items to add on the workspace */ - public AddWorkspaceItemsTask(Provider<List<ItemInfo>> appsProvider) { + public AddWorkspaceItemsTask(Provider<List<Pair<ItemInfo, Object>>> appsProvider) { mAppsProvider = appsProvider; } @Override public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { - List<ItemInfo> workspaceApps = mAppsProvider.get(); + List<Pair<ItemInfo, Object>> workspaceApps = mAppsProvider.get(); if (workspaceApps.isEmpty()) { return; } @@ -63,13 +68,17 @@ public class AddWorkspaceItemsTask extends ExtendedModelTask { final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>(); final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<>(); + HashMap<UserHandle, UserFolderInfo> userFolderMap = new HashMap<>(); // Get the list of workspace screens. We need to append to this list and // can not use sBgWorkspaceScreens because loadWorkspace() may not have been // called. ArrayList<Long> workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context); synchronized(dataModel) { - for (ItemInfo item : workspaceApps) { + + List<ItemInfo> filteredItems = new ArrayList<>(); + for (Pair<ItemInfo, Object> entry : workspaceApps) { + ItemInfo item = entry.first; if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { // Short-circuit this logic if the icon exists somewhere on the workspace @@ -78,6 +87,32 @@ public class AddWorkspaceItemsTask extends ExtendedModelTask { } } + if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { + if (item instanceof AppInfo) { + item = ((AppInfo) item).makeShortcut(); + } + + if (!Process.myUserHandle().equals(item.user)) { + // Check if this belongs to a work folder. + if (!(entry.second instanceof LauncherActivityInfo)) { + continue; + } + + UserFolderInfo userFolderInfo = userFolderMap.get(item.user); + if (userFolderInfo == null) { + userFolderInfo = new UserFolderInfo(context, item.user, dataModel); + userFolderMap.put(item.user, userFolderInfo); + } + item = userFolderInfo.convertToWorkspaceItem( + (ShortcutInfo) item, (LauncherActivityInfo) entry.second); + } + } + if (item != null) { + filteredItems.add(item); + } + } + + for (ItemInfo item : filteredItems) { // Find appropriate space for the item. Pair<Long, int[]> coords = findSpaceForItem(app, dataModel, workspaceScreens, addedWorkspaceScreensFinal, item.spanX, item.spanY); @@ -129,6 +164,10 @@ public class AddWorkspaceItemsTask extends ExtendedModelTask { } }); } + + for (UserFolderInfo userFolderInfo : userFolderMap.values()) { + userFolderInfo.applyPendingState(getModelWriter()); + } } protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) { @@ -140,7 +179,7 @@ public class AddWorkspaceItemsTask extends ExtendedModelTask { * the workspace has been loaded. We identify a shortcut by its intent. */ protected boolean shortcutExists(BgDataModel dataModel, Intent intent, UserHandle user) { - final String intentWithPkg, intentWithoutPkg; + final String compPkgName, intentWithPkg, intentWithoutPkg; if (intent == null) { // Skip items with null intents return true; @@ -148,19 +187,21 @@ public class AddWorkspaceItemsTask extends ExtendedModelTask { if (intent.getComponent() != null) { // If component is not null, an intent with null package will produce // the same result and should also be a match. - String packageName = intent.getComponent().getPackageName(); + compPkgName = intent.getComponent().getPackageName(); if (intent.getPackage() != null) { intentWithPkg = intent.toUri(0); intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0); } else { - intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0); + intentWithPkg = new Intent(intent).setPackage(compPkgName).toUri(0); intentWithoutPkg = intent.toUri(0); } } else { + compPkgName = null; intentWithPkg = intent.toUri(0); intentWithoutPkg = intent.toUri(0); } + boolean isLauncherAppTarget = Utilities.isLauncherAppTarget(intent); synchronized (dataModel) { for (ItemInfo item : dataModel.itemsIdMap) { if (item instanceof ShortcutInfo) { @@ -172,6 +213,16 @@ public class AddWorkspaceItemsTask extends ExtendedModelTask { if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) { return true; } + + // checking for existing promise icon with same package name + if (isLauncherAppTarget + && info.isPromise() + && info.hasStatusFlag(ShortcutInfo.FLAG_AUTOINSTALL_ICON) + && info.getTargetComponent() != null + && compPkgName != null + && compPkgName.equals(info.getTargetComponent().getPackageName())) { + return true; + } } } } @@ -263,4 +314,5 @@ public class AddWorkspaceItemsTask extends ExtendedModelTask { } return occupied.findVacantCell(xy, spanX, spanY); } + } diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index 0e73ca6d3..d9c5143f2 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -27,16 +27,14 @@ import com.android.launcher3.ItemInfo; 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.logging.LoggerUtils; +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.model.nano.LauncherDumpProto.ItemType; +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.LongArrayMap; import com.android.launcher3.util.MultiHashMap; @@ -93,11 +91,21 @@ public class BgDataModel { public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>(); /** + * True if the launcher has permission to access deep shortcuts. + */ + public boolean hasShortcutHostPermission; + + /** * Maps all launcher activities to the id's of their shortcuts (if they have any). */ public final MultiHashMap<ComponentKey, String> deepShortcutMap = new MultiHashMap<>(); /** + * Entire list of widgets. + */ + public final WidgetsModel widgetsModel = new WidgetsModel(); + + /** * Clears all the data */ public synchronized void clear() { @@ -242,7 +250,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/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java index 46130fc0b..d7cece488 100644 --- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java +++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java @@ -26,7 +26,6 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel.CallbackTask; import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherSettings; -import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.ShortcutInfo; import java.util.ArrayList; diff --git a/src/com/android/launcher3/model/DbDowngradeHelper.java b/src/com/android/launcher3/model/DbDowngradeHelper.java new file mode 100644 index 000000000..cd86b728b --- /dev/null +++ b/src/com/android/launcher3/model/DbDowngradeHelper.java @@ -0,0 +1,108 @@ +/* + * 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.model; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.util.Log; +import android.util.SparseArray; + +import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; +import com.android.launcher3.util.IOUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; + +/** + * Utility class to handle DB downgrade + */ +public class DbDowngradeHelper { + + private static final String TAG = "DbDowngradeHelper"; + + private static final String KEY_VERSION = "version"; + private static final String KEY_DOWNGRADE_TO = "downgrade_to_"; + + private final SparseArray<String[]> mStatements = new SparseArray<>(); + public final int version; + + private DbDowngradeHelper(int version) { + this.version = version; + } + + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + ArrayList<String> allCommands = new ArrayList<>(); + + for (int i = oldVersion - 1; i >= newVersion; i--) { + String[] commands = mStatements.get(i); + if (commands == null) { + throw new SQLiteException("Downgrade path not supported to version " + i); + } + Collections.addAll(allCommands, commands); + } + + try (SQLiteTransaction t = new SQLiteTransaction(db)) { + for (String sql : allCommands) { + db.execSQL(sql); + } + t.commit(); + } + } + + public static DbDowngradeHelper parse(File file) throws JSONException, IOException { + JSONObject obj = new JSONObject(new String(IOUtils.toByteArray(file))); + DbDowngradeHelper helper = new DbDowngradeHelper(obj.getInt(KEY_VERSION)); + for (int version = helper.version - 1; version > 0; version--) { + if (obj.has(KEY_DOWNGRADE_TO + version)) { + JSONArray statements = obj.getJSONArray(KEY_DOWNGRADE_TO + version); + String[] parsed = new String[statements.length()]; + for (int i = 0; i < parsed.length; i++) { + parsed[i] = statements.getString(i); + } + helper.mStatements.put(version, parsed); + } + } + return helper; + } + + public static void updateSchemaFile(File schemaFile, int expectedVersion, + Context context, int schemaResId) { + try { + if (DbDowngradeHelper.parse(schemaFile).version >= expectedVersion) { + return; + } + } catch (Exception e) { + // Schema error + } + + // Write the updated schema + try (FileOutputStream fos = new FileOutputStream(schemaFile); + InputStream in = context.getResources().openRawResource(schemaResId)) { + IOUtils.copy(in, fos); + } catch (IOException e) { + Log.e(TAG, "Error writing schema file", e); + } + } +} diff --git a/src/com/android/launcher3/model/ExtendedModelTask.java b/src/com/android/launcher3/model/ExtendedModelTask.java index 05419662b..080aaf54b 100644 --- a/src/com/android/launcher3/model/ExtendedModelTask.java +++ b/src/com/android/launcher3/model/ExtendedModelTask.java @@ -59,4 +59,15 @@ public abstract class ExtendedModelTask extends BaseModelUpdateTask { } }); } + + public void bindUpdatedWidgets(BgDataModel dataModel) { + final MultiHashMap<PackageItemInfo, WidgetItem> widgets + = dataModel.widgetsModel.getWidgetsMap(); + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindAllWidgets(widgets); + } + }); + } } diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index 36f60b94b..1a2c04d6e 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -219,7 +219,7 @@ public class LoaderCursor extends CursorWrapper { if (!TextUtils.isEmpty(title)) { info.title = Utilities.trim(title); } - } else if (hasRestoreFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) { + } else if (hasRestoreFlag(ShortcutInfo.FLAG_AUTOINSTALL_ICON)) { if (TextUtils.isEmpty(info.title)) { info.title = getTitle(); } diff --git a/src/com/android/launcher3/model/LoaderResults.java b/src/com/android/launcher3/model/LoaderResults.java new file mode 100644 index 000000000..28df64d39 --- /dev/null +++ b/src/com/android/launcher3/model/LoaderResults.java @@ -0,0 +1,392 @@ +/* + * 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.model; + +import android.util.Log; + +import com.android.launcher3.AllAppsList; +import com.android.launcher3.AppInfo; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppWidgetInfo; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.PagedView; +import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.MultiHashMap; +import com.android.launcher3.util.ViewOnDrawExecutor; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.Executor; + +/** + * Helper class to handle results of {@link com.android.launcher3.LauncherModel.LoaderTask}. + */ +public class LoaderResults { + + private static final String TAG = "LoaderResults"; + private static final long INVALID_SCREEN_ID = -1L; + private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons + + private final Executor mUiExecutor; + + private final LauncherAppState mApp; + private final BgDataModel mBgDataModel; + private final AllAppsList mBgAllAppsList; + private final int mPageToBindFirst; + + private final WeakReference<Callbacks> mCallbacks; + + public LoaderResults(LauncherAppState app, BgDataModel dataModel, + AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) { + mUiExecutor = new MainThreadExecutor(); + mApp = app; + mBgDataModel = dataModel; + mBgAllAppsList = allAppsList; + mPageToBindFirst = pageToBindFirst; + mCallbacks = callbacks == null ? new WeakReference<Callbacks>(null) : callbacks; + } + + /** + * Binds all loaded data to actual views on the main thread. + */ + public void bindWorkspace() { + Runnable r; + + Callbacks callbacks = mCallbacks.get(); + // Don't use these two variables in any of the callback runnables. + // Otherwise we hold a reference to them. + if (callbacks == null) { + // This launcher has exited and nobody bothered to tell us. Just bail. + Log.w(TAG, "LoaderTask running with no launcher"); + return; + } + + // Save a copy of all the bg-thread collections + ArrayList<ItemInfo> workspaceItems = new ArrayList<>(); + ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); + final ArrayList<Long> orderedScreenIds = new ArrayList<>(); + + synchronized (mBgDataModel) { + workspaceItems.addAll(mBgDataModel.workspaceItems); + appWidgets.addAll(mBgDataModel.appWidgets); + orderedScreenIds.addAll(mBgDataModel.workspaceScreens); + } + + final int currentScreen; + { + int currScreen = mPageToBindFirst != PagedView.INVALID_RESTORE_PAGE + ? mPageToBindFirst : callbacks.getCurrentWorkspaceScreen(); + if (currScreen >= orderedScreenIds.size()) { + // There may be no workspace screens (just hotseat items and an empty page). + currScreen = PagedView.INVALID_RESTORE_PAGE; + } + currentScreen = currScreen; + } + final boolean validFirstPage = currentScreen >= 0; + final long currentScreenId = + validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID; + + // Separate the items that are on the current screen, and all the other remaining items + ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>(); + ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>(); + ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>(); + ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>(); + + filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, + otherWorkspaceItems); + filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets, + otherAppWidgets); + sortWorkspaceItemsSpatially(currentWorkspaceItems); + sortWorkspaceItemsSpatially(otherWorkspaceItems); + + // Tell the workspace that we're about to start binding items + r = new Runnable() { + public void run() { + Callbacks callbacks = mCallbacks.get(); + if (callbacks != null) { + callbacks.clearPendingBinds(); + callbacks.startBinding(); + } + } + }; + mUiExecutor.execute(r); + + // Bind workspace screens + mUiExecutor.execute(new Runnable() { + @Override + public void run() { + Callbacks callbacks = mCallbacks.get(); + if (callbacks != null) { + callbacks.bindScreens(orderedScreenIds); + } + } + }); + + Executor mainExecutor = mUiExecutor; + // Load items on the current page. + bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, mainExecutor); + + // In case of validFirstPage, only bind the first screen, and defer binding the + // remaining screens after first onDraw (and an optional the fade animation whichever + // happens later). + // 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(mUiExecutor) : mainExecutor; + + mainExecutor.execute(new Runnable() { + @Override + public void run() { + Callbacks callbacks = mCallbacks.get(); + if (callbacks != null) { + callbacks.finishFirstPageBind( + validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null); + } + } + }); + + bindWorkspaceItems(otherWorkspaceItems, otherAppWidgets, deferredExecutor); + + // Tell the workspace that we're done binding items + r = new Runnable() { + public void run() { + Callbacks callbacks = mCallbacks.get(); + if (callbacks != null) { + callbacks.finishBindingItems(); + } + } + }; + deferredExecutor.execute(r); + + if (validFirstPage) { + r = new Runnable() { + public void run() { + Callbacks callbacks = mCallbacks.get(); + if (callbacks != null) { + // We are loading synchronously, which means, some of the pages will be + // bound after first draw. Inform the callbacks that page binding is + // not complete, and schedule the remaining pages. + if (currentScreen != PagedView.INVALID_RESTORE_PAGE) { + callbacks.onPageBoundSynchronously(currentScreen); + } + callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor); + } + } + }; + mUiExecutor.execute(r); + } + } + + + /** Filters the set of items who are directly or indirectly (via another container) on the + * specified screen. */ + private void filterCurrentWorkspaceItems(long currentScreenId, + ArrayList<ItemInfo> allWorkspaceItems, + ArrayList<ItemInfo> currentScreenItems, + ArrayList<ItemInfo> otherScreenItems) { + // Purge any null ItemInfos + Iterator<ItemInfo> iter = allWorkspaceItems.iterator(); + while (iter.hasNext()) { + ItemInfo i = iter.next(); + if (i == null) { + iter.remove(); + } + } + + // Order the set of items by their containers first, this allows use to walk through the + // list sequentially, build up a list of containers that are in the specified screen, + // as well as all items in those containers. + Set<Long> itemsOnScreen = new HashSet<Long>(); + Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() { + @Override + public int compare(ItemInfo lhs, ItemInfo rhs) { + return Utilities.longCompare(lhs.container, rhs.container); + } + }); + for (ItemInfo info : allWorkspaceItems) { + if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + if (info.screenId == currentScreenId) { + currentScreenItems.add(info); + itemsOnScreen.add(info.id); + } else { + otherScreenItems.add(info); + } + } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + currentScreenItems.add(info); + itemsOnScreen.add(info.id); + } else { + if (itemsOnScreen.contains(info.container)) { + currentScreenItems.add(info); + itemsOnScreen.add(info.id); + } else { + otherScreenItems.add(info); + } + } + } + } + + /** Filters the set of widgets which are on the specified screen. */ + private void filterCurrentAppWidgets(long currentScreenId, + ArrayList<LauncherAppWidgetInfo> appWidgets, + ArrayList<LauncherAppWidgetInfo> currentScreenWidgets, + ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) { + + for (LauncherAppWidgetInfo widget : appWidgets) { + if (widget == null) continue; + if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && + widget.screenId == currentScreenId) { + currentScreenWidgets.add(widget); + } else { + otherScreenWidgets.add(widget); + } + } + } + + /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to + * right) */ + private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) { + final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile(); + final int screenCols = profile.numColumns; + final int screenCellCount = profile.numColumns * profile.numRows; + Collections.sort(workspaceItems, new Comparator<ItemInfo>() { + @Override + public int compare(ItemInfo lhs, ItemInfo rhs) { + if (lhs.container == rhs.container) { + // Within containers, order by their spatial position in that container + switch ((int) lhs.container) { + case LauncherSettings.Favorites.CONTAINER_DESKTOP: { + long lr = (lhs.screenId * screenCellCount + + lhs.cellY * screenCols + lhs.cellX); + long rr = (rhs.screenId * screenCellCount + + rhs.cellY * screenCols + rhs.cellX); + return Utilities.longCompare(lr, rr); + } + case LauncherSettings.Favorites.CONTAINER_HOTSEAT: { + // We currently use the screen id as the rank + return Utilities.longCompare(lhs.screenId, rhs.screenId); + } + default: + if (FeatureFlags.IS_DOGFOOD_BUILD) { + throw new RuntimeException("Unexpected container type when " + + "sorting workspace items."); + } + return 0; + } + } else { + // Between containers, order by hotseat, desktop + return Utilities.longCompare(lhs.container, rhs.container); + } + } + }); + } + + private void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems, + final ArrayList<LauncherAppWidgetInfo> appWidgets, + final Executor executor) { + + // Bind the workspace items + int N = workspaceItems.size(); + for (int i = 0; i < N; i += ITEMS_CHUNK) { + final int start = i; + final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); + final Runnable r = new Runnable() { + @Override + public void run() { + Callbacks callbacks = mCallbacks.get(); + if (callbacks != null) { + callbacks.bindItems(workspaceItems, start, start+chunkSize, false); + } + } + }; + executor.execute(r); + } + + // Bind the widgets, one at a time + N = appWidgets.size(); + for (int i = 0; i < N; i++) { + final LauncherAppWidgetInfo widget = appWidgets.get(i); + final Runnable r = new Runnable() { + public void run() { + Callbacks callbacks = mCallbacks.get(); + if (callbacks != null) { + callbacks.bindAppWidget(widget); + } + } + }; + executor.execute(r); + } + } + + public void bindDeepShortcuts() { + final MultiHashMap<ComponentKey, String> shortcutMapCopy; + synchronized (mBgDataModel) { + shortcutMapCopy = mBgDataModel.deepShortcutMap.clone(); + } + Runnable r = new Runnable() { + @Override + public void run() { + Callbacks callbacks = mCallbacks.get(); + if (callbacks != null) { + callbacks.bindDeepShortcutMap(shortcutMapCopy); + } + } + }; + mUiExecutor.execute(r); + } + + public void bindAllApps() { + // shallow copy + @SuppressWarnings("unchecked") + final ArrayList<AppInfo> list = (ArrayList<AppInfo>) mBgAllAppsList.data.clone(); + + Runnable r = new Runnable() { + public void run() { + Callbacks callbacks = mCallbacks.get(); + if (callbacks != null) { + callbacks.bindAllApplications(list); + } + } + }; + mUiExecutor.execute(r); + } + + public void bindWidgets() { + final MultiHashMap<PackageItemInfo, WidgetItem> widgets + = mBgDataModel.widgetsModel.getWidgetsMap(); + Runnable r = new Runnable() { + public void run() { + Callbacks callbacks = mCallbacks.get(); + if (callbacks != null) { + callbacks.bindAllWidgets(widgets); + } + } + }; + mUiExecutor.execute(r); + } +} diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java new file mode 100644 index 000000000..bcf516eed --- /dev/null +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -0,0 +1,839 @@ +/* + * 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.model; + +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.PackageInstaller; +import android.os.Handler; +import android.os.Looper; +import android.os.Process; +import android.os.SystemClock; +import android.os.Trace; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; +import android.util.LongSparseArray; +import android.util.MutableInt; + +import com.android.launcher3.AllAppsList; +import com.android.launcher3.AppInfo; +import com.android.launcher3.FolderInfo; +import com.android.launcher3.IconCache; +import com.android.launcher3.InstallShortcutReceiver; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppWidgetInfo; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.Utilities; +import com.android.launcher3.compat.AppWidgetManagerCompat; +import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.PackageInstallerCompat; +import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.config.FeatureFlags; +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.provider.ImportDataTask; +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.LooperIdleLock; +import com.android.launcher3.util.ManagedProfileHeuristic; +import com.android.launcher3.util.MultiHashMap; +import com.android.launcher3.util.PackageManagerHelper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CancellationException; + +/** + * Runnable for the thread that loads the contents of the launcher: + * - workspace icons + * - widgets + * - all apps icons + * - deep shortcuts within apps + */ +public class LoaderTask implements Runnable { + private static final boolean DEBUG_LOADERS = false; + private static final String TAG = "LoaderTask"; + + private final LauncherAppState mApp; + private final AllAppsList mBgAllAppsList; + private final BgDataModel mBgDataModel; + + private final LoaderResults mResults; + + private final LauncherAppsCompat mLauncherApps; + private final UserManagerCompat mUserManager; + private final DeepShortcutManager mShortcutManager; + private final PackageInstallerCompat mPackageInstaller; + private final AppWidgetManagerCompat mAppWidgetManager; + private final IconCache mIconCache; + + private boolean mStopped; + + public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel, + LoaderResults results) { + mApp = app; + mBgAllAppsList = bgAllAppsList; + mBgDataModel = dataModel; + mResults = results; + + mLauncherApps = LauncherAppsCompat.getInstance(mApp.getContext()); + mUserManager = UserManagerCompat.getInstance(mApp.getContext()); + mShortcutManager = DeepShortcutManager.getInstance(mApp.getContext()); + mPackageInstaller = PackageInstallerCompat.getInstance(mApp.getContext()); + mAppWidgetManager = AppWidgetManagerCompat.getInstance(mApp.getContext()); + mIconCache = mApp.getIconCache(); + } + + private synchronized 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. + 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)); + } + + private synchronized void verifyNotStopped() throws CancellationException { + if (mStopped) { + throw new CancellationException("Loader stopped"); + } + } + + public void run() { + synchronized (this) { + // Skip fast if we are already stopped. + if (mStopped) { + return; + } + } + + try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) { + long now = 0; + if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace"); + loadWorkspace(); + + verifyNotStopped(); + if (DEBUG_LOADERS) Log.d(TAG, "step 1.2: bind workspace workspace"); + mResults.bindWorkspace(); + + // 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.1: loading all apps"); + loadAllApps(); + + if (DEBUG_LOADERS) Log.d(TAG, "step 2.2: Binding all apps"); + verifyNotStopped(); + mResults.bindAllApps(); + + verifyNotStopped(); + if (DEBUG_LOADERS) Log.d(TAG, "step 2.3: 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.1: loading deep shortcuts"); + loadDeepShortcuts(); + + verifyNotStopped(); + if (DEBUG_LOADERS) Log.d(TAG, "step 3.2: bind deep shortcuts"); + mResults.bindDeepShortcuts(); + + // Take a break + if (DEBUG_LOADERS) Log.d(TAG, "step 3 completed, wait for idle"); + waitForIdle(); + verifyNotStopped(); + + // fourth step + if (DEBUG_LOADERS) Log.d(TAG, "step 4.1: loading widgets"); + mBgDataModel.widgetsModel.update(mApp, null); + + verifyNotStopped(); + if (DEBUG_LOADERS) Log.d(TAG, "step 4.2: Binding widgets"); + mResults.bindWidgets(); + + transaction.commit(); + } catch (CancellationException e) { + // Loader stopped, ignore + } + } + + public synchronized void stopLocked() { + mStopped = true; + this.notify(); + } + + private void loadWorkspace() { + if (LauncherAppState.PROFILE_STARTUP) { + Trace.beginSection("Loading Workspace"); + } + + final Context context = mApp.getContext(); + final ContentResolver contentResolver = context.getContentResolver(); + final PackageManagerHelper pmHelper = new PackageManagerHelper(context); + final boolean isSafeMode = pmHelper.isSafeMode(); + final boolean isSdCardReady = Utilities.isBootCompleted(); + final MultiHashMap<UserHandle, String> pendingPackages = new MultiHashMap<>(); + + boolean clearDb = false; + try { + ImportDataTask.performImportIfPossible(context); + } catch (Exception e) { + // Migration failed. Clear workspace. + clearDb = true; + } + + if (!clearDb && GridSizeMigrationTask.ENABLED && + !GridSizeMigrationTask.migrateGridIfNeeded(context)) { + // Migration failed. Clear workspace. + clearDb = true; + } + + if (clearDb) { + Log.d(TAG, "loadWorkspace: resetting launcher database"); + LauncherSettings.Settings.call(contentResolver, + LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); + } + + Log.d(TAG, "loadWorkspace: loading default favorites"); + LauncherSettings.Settings.call(contentResolver, + LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES); + + synchronized (mBgDataModel) { + mBgDataModel.clear(); + + final HashMap<String, Integer> installingPkgs = + mPackageInstaller.updateAndGetActiveSessionCache(); + mBgDataModel.workspaceScreens.addAll(LauncherModel.loadWorkspaceScreensDb(context)); + + Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>(); + final LoaderCursor c = new LoaderCursor(contentResolver.query( + LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp); + + HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null; + + try { + final int appWidgetIdIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.APPWIDGET_ID); + final int appWidgetProviderIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.APPWIDGET_PROVIDER); + final int spanXIndex = c.getColumnIndexOrThrow + (LauncherSettings.Favorites.SPANX); + final int spanYIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.SPANY); + final int rankIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.RANK); + final int optionsIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.OPTIONS); + + final LongSparseArray<UserHandle> allUsers = c.allUsers; + final LongSparseArray<Boolean> quietMode = new LongSparseArray<>(); + final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>(); + for (UserHandle user : mUserManager.getUserProfiles()) { + long serialNo = mUserManager.getSerialNumberForUser(user); + allUsers.put(serialNo, user); + quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user)); + + boolean userUnlocked = mUserManager.isUserUnlocked(user); + + // We can only query for shortcuts when the user is unlocked. + if (userUnlocked) { + List<ShortcutInfoCompat> pinnedShortcuts = + mShortcutManager.queryForPinnedShortcuts(null, user); + if (mShortcutManager.wasLastCallSuccess()) { + for (ShortcutInfoCompat shortcut : pinnedShortcuts) { + shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), + shortcut); + } + } else { + // Shortcut manager can fail due to some race condition when the + // lock state changes too frequently. For the purpose of the loading + // shortcuts, consider the user is still locked. + userUnlocked = false; + } + } + unlockedUsers.put(serialNo, userUnlocked); + } + + ShortcutInfo info; + LauncherAppWidgetInfo appWidgetInfo; + Intent intent; + String targetPkg; + + FolderIconPreviewVerifier verifier = + new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile()); + while (!mStopped && c.moveToNext()) { + try { + if (c.user == null) { + // User has been deleted, remove the item. + c.markDeleted("User has been deleted"); + continue; + } + + boolean allowMissingTarget = false; + switch (c.itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: + intent = c.parseIntent(); + if (intent == null) { + c.markDeleted("Invalid or null intent"); + continue; + } + + int disabledState = quietMode.get(c.serialNumber) ? + ShortcutInfo.FLAG_DISABLED_QUIET_USER : 0; + ComponentName cn = intent.getComponent(); + targetPkg = cn == null ? intent.getPackage() : cn.getPackageName(); + + if (!Process.myUserHandle().equals(c.user)) { + if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { + c.markDeleted("Legacy shortcuts are only allowed for default user"); + continue; + } else if (c.restoreFlag != 0) { + // Don't restore items for other profiles. + c.markDeleted("Restore from managed profile not supported"); + continue; + } + } + if (TextUtils.isEmpty(targetPkg) && + c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { + c.markDeleted("Only legacy shortcuts can have null package"); + continue; + } + + // If there is no target package, its an implicit intent + // (legacy shortcut) which is always valid + boolean validTarget = TextUtils.isEmpty(targetPkg) || + mLauncherApps.isPackageEnabledForProfile(targetPkg, c.user); + + if (cn != null && validTarget) { + // If the apk is present and the shortcut points to a specific + // component. + + // If the component is already present + if (mLauncherApps.isActivityEnabledForProfile(cn, c.user)) { + // no special handling necessary for this item + c.markRestored(); + } else { + if (c.hasRestoreFlag(ShortcutInfo.FLAG_AUTOINSTALL_ICON)) { + // We allow auto install apps to have their intent + // updated after an install. + intent = pmHelper.getAppLaunchIntent(targetPkg, c.user); + if (intent != null) { + c.restoreFlag = 0; + c.updater().put( + LauncherSettings.Favorites.INTENT, + intent.toUri(0)).commit(); + cn = intent.getComponent(); + } else { + c.markDeleted("Unable to find a launch target"); + continue; + } + } else { + // The app is installed but the component is no + // longer available. + c.markDeleted("Invalid component removed: " + cn); + continue; + } + } + } + // else if cn == null => can't infer much, leave it + // else if !validPkg => could be restored icon or missing sd-card + + if (!TextUtils.isEmpty(targetPkg) && !validTarget) { + // Points to a valid app (superset of cn != null) but the apk + // is not available. + + if (c.restoreFlag != 0) { + // Package is not yet available but might be + // installed later. + FileLog.d(TAG, "package not yet restored: " + targetPkg); + + if (c.hasRestoreFlag(ShortcutInfo.FLAG_RESTORE_STARTED)) { + // Restore has started once. + } else if (installingPkgs.containsKey(targetPkg)) { + // App restore has started. Update the flag + c.restoreFlag |= ShortcutInfo.FLAG_RESTORE_STARTED; + c.updater().commit(); + } else { + c.markDeleted("Unrestored app removed: " + targetPkg); + continue; + } + } 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. + allowMissingTarget = true; + } else if (!isSdCardReady) { + // SdCard is not ready yet. Package might get available, + // once it is ready. + Log.d(TAG, "Missing pkg, will check later: " + targetPkg); + pendingPackages.addToList(c.user, targetPkg); + // Add the icon on the workspace anyway. + allowMissingTarget = true; + } else { + // Do not wait for external media load anymore. + c.markDeleted("Invalid package removed: " + targetPkg); + continue; + } + } + + if (validTarget) { + // The shortcut points to a valid target (either no target + // or something which is ready to be used) + c.markRestored(); + } + + boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() && + !verifier.isItemInPreview(c.getInt(rankIndex)); + + if (c.restoreFlag != 0) { + // Already verified above that user is same as default user + info = c.getRestoredItemInfo(intent); + } else if (c.itemType == + LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { + info = c.getAppShortcutInfo( + intent, allowMissingTarget, useLowResIcon); + } else if (c.itemType == + LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { + + ShortcutKey key = ShortcutKey.fromIntent(intent, c.user); + if (unlockedUsers.get(c.serialNumber)) { + ShortcutInfoCompat pinnedShortcut = + shortcutKeyToPinnedShortcuts.get(key); + if (pinnedShortcut == null) { + // The shortcut is no longer valid. + c.markDeleted("Pinned shortcut not found"); + continue; + } + 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. + info = c.loadSimpleShortcut(); + info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER; + } + } else { // item type == ITEM_TYPE_SHORTCUT + info = c.loadSimpleShortcut(); + + // Shortcuts are only available on the primary profile + if (!TextUtils.isEmpty(targetPkg) + && pmHelper.isAppSuspended(targetPkg, c.user)) { + disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; + } + + // App shortcuts that used to be automatically added to Launcher + // didn't always have the correct intent flags set, so do that + // here + if (intent.getAction() != null && + intent.getCategories() != null && + intent.getAction().equals(Intent.ACTION_MAIN) && + intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { + intent.addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + } + } + + if (info != null) { + c.applyCommonProperties(info); + + info.intent = intent; + info.rank = c.getInt(rankIndex); + info.spanX = 1; + info.spanY = 1; + info.isDisabled |= disabledState; + if (isSafeMode && !Utilities.isSystemApp(context, intent)) { + info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE; + } + + if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) { + Integer progress = installingPkgs.get(targetPkg); + if (progress != null) { + info.setInstallProgress(progress); + } else { + info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; + } + } + + c.checkAndAddItem(info, mBgDataModel); + } else { + throw new RuntimeException("Unexpected null ShortcutInfo"); + } + break; + + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + FolderInfo folderInfo = mBgDataModel.findOrMakeFolder(c.id); + c.applyCommonProperties(folderInfo); + + // Do not trim the folder label, as is was set by the user. + folderInfo.title = c.getString(c.titleIndex); + folderInfo.spanX = 1; + folderInfo.spanY = 1; + folderInfo.options = c.getInt(optionsIndex); + + // no special handling required for restored folders + c.markRestored(); + + c.checkAndAddItem(folderInfo, mBgDataModel); + break; + + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: + // Read all Launcher-specific widget details + boolean customWidget = c.itemType == + LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; + + int appWidgetId = c.getInt(appWidgetIdIndex); + String savedProvider = c.getString(appWidgetProviderIndex); + + final ComponentName component = + ComponentName.unflattenFromString(savedProvider); + + final boolean isIdValid = !c.hasRestoreFlag( + LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); + final boolean wasProviderReady = !c.hasRestoreFlag( + LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY); + + if (widgetProvidersMap == null) { + widgetProvidersMap = mAppWidgetManager.getAllProvidersMap(); + } + final AppWidgetProviderInfo provider = widgetProvidersMap.get( + new ComponentKey( + ComponentName.unflattenFromString(savedProvider), + c.user)); + + final boolean isProviderReady = isValidProvider(provider); + if (!isSafeMode && !customWidget && + wasProviderReady && !isProviderReady) { + c.markDeleted( + "Deleting widget that isn't installed anymore: " + + provider); + } else { + if (isProviderReady) { + appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, + provider.provider); + + // The provider is available. So the widget is either + // available or not available. We do not need to track + // any future restore updates. + int status = c.restoreFlag & + ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; + if (!wasProviderReady) { + // If provider was not previously ready, update the + // status and UI flag. + + // Id would be valid only if the widget restore broadcast was received. + if (isIdValid) { + status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY; + } else { + status &= ~LauncherAppWidgetInfo + .FLAG_PROVIDER_NOT_READY; + } + } + appWidgetInfo.restoreStatus = status; + } else { + Log.v(TAG, "Widget restore pending id=" + c.id + + " appWidgetId=" + appWidgetId + + " status =" + c.restoreFlag); + appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, + component); + appWidgetInfo.restoreStatus = c.restoreFlag; + Integer installProgress = installingPkgs.get(component.getPackageName()); + + if (c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED)) { + // Restore has started once. + } else if (installProgress != null) { + // App restore has started. Update the flag + appWidgetInfo.restoreStatus |= + LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; + } else if (!isSafeMode) { + c.markDeleted("Unrestored widget removed: " + component); + continue; + } + + appWidgetInfo.installProgress = + installProgress == null ? 0 : installProgress; + } + if (appWidgetInfo.hasRestoreFlag( + LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) { + appWidgetInfo.bindOptions = c.parseIntent(); + } + + c.applyCommonProperties(appWidgetInfo); + appWidgetInfo.spanX = c.getInt(spanXIndex); + appWidgetInfo.spanY = c.getInt(spanYIndex); + appWidgetInfo.user = c.user; + + if (!c.isOnWorkspaceOrHotseat()) { + c.markDeleted("Widget found where container != " + + "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); + continue; + } + + if (!customWidget) { + String providerName = + appWidgetInfo.providerName.flattenToString(); + if (!providerName.equals(savedProvider) || + (appWidgetInfo.restoreStatus != c.restoreFlag)) { + c.updater() + .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, + providerName) + .put(LauncherSettings.Favorites.RESTORED, + appWidgetInfo.restoreStatus) + .commit(); + } + } + + if (appWidgetInfo.restoreStatus != + LauncherAppWidgetInfo.RESTORE_COMPLETED) { + String pkg = appWidgetInfo.providerName.getPackageName(); + appWidgetInfo.pendingItemInfo = new PackageItemInfo(pkg); + appWidgetInfo.pendingItemInfo.user = appWidgetInfo.user; + mIconCache.getTitleAndIconForApp( + appWidgetInfo.pendingItemInfo, false); + } + + c.checkAndAddItem(appWidgetInfo, mBgDataModel); + } + break; + } + } catch (Exception e) { + Log.e(TAG, "Desktop items loading interrupted", e); + } + } + } finally { + Utilities.closeSilently(c); + } + + // Break early if we've stopped loading + if (mStopped) { + mBgDataModel.clear(); + return; + } + + // Remove dead items + if (c.commitDeleted()) { + // Remove any empty folder + ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings + .call(contentResolver, + LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS) + .getSerializable(LauncherSettings.Settings.EXTRA_VALUE); + for (long folderId : deletedFolderIds) { + mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId)); + mBgDataModel.folders.remove(folderId); + mBgDataModel.itemsIdMap.remove(folderId); + } + + // Remove any ghost widgets + LauncherSettings.Settings.call(contentResolver, + LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS); + } + + // Unpin shortcuts that don't exist on the workspace. + HashSet<ShortcutKey> pendingShortcuts = + InstallShortcutReceiver.getPendingShortcuts(context); + for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) { + MutableInt numTimesPinned = mBgDataModel.pinnedShortcutCounts.get(key); + if ((numTimesPinned == null || numTimesPinned.value == 0) + && !pendingShortcuts.contains(key)) { + // Shortcut is pinned but doesn't exist on the workspace; unpin it. + mShortcutManager.unpinShortcut(key); + } + } + + FolderIconPreviewVerifier verifier = + new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile()); + // Sort the folder items and make sure all items in the preview are high resolution. + for (FolderInfo folder : mBgDataModel.folders) { + Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR); + verifier.setFolderInfo(folder); + + int numItemsInPreview = 0; + for (ShortcutInfo info : folder.contents) { + if (info.usingLowResIcon + && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION + && verifier.isItemInPreview(info.rank)) { + mIconCache.getTitleAndIcon(info, false); + numItemsInPreview++; + } + + if (numItemsInPreview >= FolderIcon.NUM_ITEMS_IN_PREVIEW) { + break; + } + } + } + + c.commitRestoredItems(); + if (!isSdCardReady && !pendingPackages.isEmpty()) { + context.registerReceiver( + new SdCardAvailableReceiver(mApp, pendingPackages), + new IntentFilter(Intent.ACTION_BOOT_COMPLETED), + null, + new Handler(LauncherModel.getWorkerLooper())); + } + + // Remove any empty screens + ArrayList<Long> unusedScreens = new ArrayList<>(mBgDataModel.workspaceScreens); + for (ItemInfo item: mBgDataModel.itemsIdMap) { + long screenId = item.screenId; + if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && + unusedScreens.contains(screenId)) { + unusedScreens.remove(screenId); + } + } + + // If there are any empty screens remove them, and update. + if (unusedScreens.size() != 0) { + mBgDataModel.workspaceScreens.removeAll(unusedScreens); + LauncherModel.updateWorkspaceScreenOrder(context, mBgDataModel.workspaceScreens); + } + } + if (LauncherAppState.PROFILE_STARTUP) { + Trace.endSection(); + } + } + + private void updateIconCache() { + // Ignore packages which have a promise icon. + HashSet<String> packagesToIgnore = new HashSet<>(); + synchronized (mBgDataModel) { + for (ItemInfo info : mBgDataModel.itemsIdMap) { + if (info instanceof ShortcutInfo) { + ShortcutInfo si = (ShortcutInfo) info; + if (si.isPromise() && si.getTargetComponent() != null) { + packagesToIgnore.add(si.getTargetComponent().getPackageName()); + } + } else if (info instanceof LauncherAppWidgetInfo) { + LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info; + if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) { + packagesToIgnore.add(lawi.providerName.getPackageName()); + } + } + } + } + mIconCache.updateDbIcons(packagesToIgnore); + } + + private void loadAllApps() { + final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; + + final List<UserHandle> profiles = mUserManager.getUserProfiles(); + + // Clear the list of apps + mBgAllAppsList.clear(); + for (UserHandle user : profiles) { + // Query for the set of apps + final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; + final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user); + if (DEBUG_LOADERS) { + Log.d(TAG, "getActivityList took " + + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user); + Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user); + } + // Fail if we don't have any apps + // TODO: Fix this. Only fail for the current user. + if (apps == null || apps.isEmpty()) { + return; + } + boolean quietMode = mUserManager.isQuietModeEnabled(user); + // Create the ApplicationInfos + for (int i = 0; i < apps.size(); i++) { + LauncherActivityInfo app = apps.get(i); + // This builds the icon bitmaps. + mBgAllAppsList.add(new AppInfo(app, user, quietMode), app); + } + + ManagedProfileHeuristic.onAllAppsLoaded(mApp.getContext(), apps, user); + } + + if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) { + // get all active sessions and add them to the all apps list + for (PackageInstaller.SessionInfo info : + mPackageInstaller.getAllVerifiedSessions()) { + mBgAllAppsList.addPromiseApp(mApp.getContext(), + PackageInstallerCompat.PackageInstallInfo.fromInstallingState(info)); + } + } + + mBgAllAppsList.added = new ArrayList<>(); + if (DEBUG_LOADERS) { + Log.d(TAG, "All apps loaded in in " + + (SystemClock.uptimeMillis() - loadTime) + "ms"); + } + } + + private void loadDeepShortcuts() { + mBgDataModel.deepShortcutMap.clear(); + mBgDataModel.hasShortcutHostPermission = mShortcutManager.hasHostPermission(); + if (mBgDataModel.hasShortcutHostPermission) { + for (UserHandle user : mUserManager.getUserProfiles()) { + if (mUserManager.isUserUnlocked(user)) { + List<ShortcutInfoCompat> shortcuts = + mShortcutManager.queryForAllShortcuts(user); + mBgDataModel.updateDeepShortcutMap(null, user, shortcuts); + } + } + } + } + + public static boolean isValidProvider(AppWidgetProviderInfo provider) { + return (provider != null) && (provider.provider != null) + && (provider.provider.getPackageName() != null); + } +} 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/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java index 5d04325e8..76b90a8e2 100644 --- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java +++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java @@ -18,15 +18,18 @@ package com.android.launcher3.model; import android.content.ComponentName; import com.android.launcher3.AllAppsList; +import com.android.launcher3.AppInfo; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherModel.CallbackTask; import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.PromiseAppInfo; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; +import java.util.ArrayList; import java.util.HashSet; /** @@ -47,6 +50,44 @@ public class PackageInstallStateChangedTask extends ExtendedModelTask { return; } + synchronized (apps) { + PromiseAppInfo updated = null; + final ArrayList<AppInfo> removed = new ArrayList<>(); + for (int i=0; i < apps.size(); i++) { + final AppInfo appInfo = apps.get(i); + final ComponentName tgtComp = appInfo.getTargetComponent(); + if (tgtComp != null && tgtComp.getPackageName().equals(mInstallInfo.packageName)) { + if (appInfo instanceof PromiseAppInfo) { + final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo; + if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLING) { + promiseAppInfo.level = mInstallInfo.progress; + updated = promiseAppInfo; + } else if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) { + apps.removePromiseApp(appInfo); + removed.add(appInfo); + } + } + } + } + if (updated != null) { + final PromiseAppInfo updatedPromiseApp = updated; + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindPromiseAppProgressUpdated(updatedPromiseApp); + } + }); + } + if (!removed.isEmpty()) { + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindAppInfosRemoved(removed); + } + }); + } + } + synchronized (dataModel) { final HashSet<ItemInfo> updates = new HashSet<>(); for (ItemInfo info : dataModel.itemsIdMap) { @@ -56,7 +97,6 @@ public class PackageInstallStateChangedTask extends ExtendedModelTask { if (si.isPromise() && (cn != null) && mInstallInfo.packageName.equals(cn.getPackageName())) { si.setInstallProgress(mInstallInfo.progress); - if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) { // Mark this info as broken. si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index f03c9c77c..46fea218f 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -18,9 +18,8 @@ package com.android.launcher3.model; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.graphics.Bitmap; +import android.os.Process; import android.os.UserHandle; import android.util.Log; @@ -36,14 +35,17 @@ import com.android.launcher3.LauncherModel.CallbackTask; import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.SessionCommitReceiver; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.ItemInfoMatcher; -import com.android.launcher3.util.ManagedProfileHeuristic; +import com.android.launcher3.util.MultiHashMap; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; @@ -95,12 +97,15 @@ public class PackageUpdatedTask extends ExtendedModelTask { for (int i = 0; i < N; i++) { if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]); iconCache.updateIconsForPkg(packages[i], mUser); + if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) { + appsList.removePackage(packages[i], Process.myUserHandle()); + } appsList.addPackage(context, packages[i], mUser); - } - ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser); - if (heuristic != null) { - heuristic.processPackageAdd(mPackages); + // Automatically add homescreen icon for work profile apps for below O device. + if (!Utilities.isAtLeastO() && !Process.myUserHandle().equals(mUser)) { + SessionCommitReceiver.queueAppIconAddition(context, packages[i], mUser); + } } break; } @@ -115,10 +120,6 @@ public class PackageUpdatedTask extends ExtendedModelTask { flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE); break; case OP_REMOVE: { - ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser); - if (heuristic != null) { - heuristic.processPackageRemoved(mPackages); - } for (int i = 0; i < N; i++) { iconCache.removeIconsForPkg(packages[i], mUser); } @@ -222,18 +223,17 @@ public class PackageUpdatedTask extends ExtendedModelTask { if (cn != null && matcher.matches(si, cn)) { AppInfo appInfo = addedOrUpdatedApps.get(cn); - if (si.isPromise() && mOp == OP_ADD) { - if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) { + // For system apps, package manager send OP_UPDATE when an + // app is enabled. + if (si.isPromise() && (mOp == OP_ADD || mOp == OP_UPDATE)) { + if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINSTALL_ICON)) { // Auto install icon - PackageManager pm = context.getPackageManager(); - ResolveInfo matched = pm.resolveActivity( - new Intent(Intent.ACTION_MAIN) - .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER), - PackageManager.MATCH_DEFAULT_ONLY); - if (matched == null) { + LauncherAppsCompat launcherApps + = LauncherAppsCompat.getInstance(context); + if (!launcherApps.isActivityEnabledForProfile(cn, mUser)) { // Try to find the best match activity. - Intent intent = pm.getLaunchIntentForPackage( - cn.getPackageName()); + Intent intent = new PackageManagerHelper(context) + .getAppLaunchIntent(cn.getPackageName(), mUser); if (intent != null) { cn = intent.getComponent(); appInfo = addedOrUpdatedApps.get(cn); @@ -374,11 +374,9 @@ public class PackageUpdatedTask extends ExtendedModelTask { } else if (Utilities.isAtLeastO() && mOp == OP_ADD) { // Load widgets for the new package. for (int i = 0; i < N; i++) { - LauncherModel model = app.getModel(); - model.refreshAndBindWidgetsAndShortcuts( - model.getCallback(), false /* bindFirst */, - new PackageUserKey(packages[i], mUser) /* packageUser */); + dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser)); } + bindUpdatedWidgets(dataModel); } } } diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java index 278669bdb..3aedae69a 100644 --- a/src/com/android/launcher3/model/SdCardAvailableReceiver.java +++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java @@ -19,9 +19,9 @@ package com.android.launcher3.model; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.os.UserHandle; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.util.MultiHashMap; @@ -44,10 +44,10 @@ public class SdCardAvailableReceiver extends BroadcastReceiver { private final Context mContext; private final MultiHashMap<UserHandle, String> mPackages; - public SdCardAvailableReceiver(LauncherModel model, Context context, + public SdCardAvailableReceiver(LauncherAppState app, MultiHashMap<UserHandle, String> packages) { - mModel = model; - mContext = context; + mModel = app.getModel(); + mContext = app.getContext(); mPackages = packages; } diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java index d8a429cba..47e83e5b9 100644 --- a/src/com/android/launcher3/model/ShortcutsChangedTask.java +++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java @@ -21,7 +21,6 @@ import android.os.UserHandle; import com.android.launcher3.AllAppsList; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.graphics.LauncherIcons; diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java index 363f1eeb1..fefed7552 100644 --- a/src/com/android/launcher3/model/UserLockStateChangedTask.java +++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java @@ -21,7 +21,6 @@ import android.os.UserHandle; import com.android.launcher3.AllAppsList; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.compat.UserManagerCompat; diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java index e5215c70f..ed900bf35 100644 --- a/src/com/android/launcher3/model/WidgetsModel.java +++ b/src/com/android/launcher3/model/WidgetsModel.java @@ -18,7 +18,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.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; @@ -38,36 +38,26 @@ public class WidgetsModel { private static final boolean DEBUG = false; /* Map of widgets and shortcuts that are tracked per package. */ - private final MultiHashMap<PackageItemInfo, WidgetItem> mWidgetsList; + private final MultiHashMap<PackageItemInfo, WidgetItem> mWidgetsList = new MultiHashMap<>(); - private final IconCache mIconCache; - private final AppFilter mAppFilter; + private AppFilter mAppFilter; - public WidgetsModel(IconCache iconCache, AppFilter appFilter) { - mIconCache = iconCache; - mAppFilter = appFilter; - mWidgetsList = new MultiHashMap<>(); - } - - public MultiHashMap<PackageItemInfo, WidgetItem> getWidgetsMap() { - return mWidgetsList; - } - - public boolean isEmpty() { - return mWidgetsList.isEmpty(); + public synchronized MultiHashMap<PackageItemInfo, WidgetItem> getWidgetsMap() { + return mWidgetsList.clone(); } /** * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise * only widgets and shortcuts associated with the package/user are. */ - public ArrayList<WidgetItem> update(Context context, @Nullable PackageUserKey packageUser) { + public void update(LauncherAppState app, @Nullable PackageUserKey packageUser) { Preconditions.assertWorkerThread(); + Context context = app.getContext(); final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>(); try { PackageManager pm = context.getPackageManager(); - InvariantDeviceProfile idp = LauncherAppState.getIDP(context); + InvariantDeviceProfile idp = app.getInvariantDeviceProfile(); // Widgets AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(context); @@ -81,9 +71,9 @@ public class WidgetsModel { .getCustomShortcutActivityList(packageUser)) { widgetsAndShortcuts.add(new WidgetItem(info)); } - setWidgetsAndShortcuts(widgetsAndShortcuts, context, packageUser); + setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser); } 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 @@ -92,11 +82,12 @@ public class WidgetsModel { throw e; } } - return widgetsAndShortcuts; + + app.getWidgetCache().removeObsoletePreviews(widgetsAndShortcuts, packageUser); } - private void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts, - Context context, @Nullable PackageUserKey packageUser) { + private synchronized void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts, + LauncherAppState app, @Nullable PackageUserKey packageUser) { if (DEBUG) { Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size()); } @@ -133,7 +124,7 @@ public class WidgetsModel { } } - InvariantDeviceProfile idp = LauncherAppState.getIDP(context); + InvariantDeviceProfile idp = app.getInvariantDeviceProfile(); UserHandle myUser = Process.myUserHandle(); // add and update. @@ -152,6 +143,9 @@ public class WidgetsModel { } } + if (mAppFilter == null) { + mAppFilter = AppFilter.newInstance(app.getContext()); + } if (!mAppFilter.shouldShowApp(item.componentName)) { if (DEBUG) { Log.d(TAG, String.format("%s is filtered and not added to the widget tray.", @@ -174,8 +168,9 @@ public class WidgetsModel { } // Update each package entry + IconCache iconCache = app.getIconCache(); for (PackageItemInfo p : tmpPackageItemInfos.values()) { - mIconCache.getTitleAndIconForApp(p, true /* userLowResIcon */); + iconCache.getTitleAndIconForApp(p, true /* userLowResIcon */); } } }
\ No newline at end of file diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java index 051c0333d..b83c9b95d 100644 --- a/src/com/android/launcher3/notification/NotificationFooterLayout.java +++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java @@ -205,6 +205,7 @@ public class NotificationFooterLayout extends FrameLayout { collapseFooter.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { + ((ViewGroup) getParent()).findViewById(R.id.divider).setVisibility(GONE); ((ViewGroup) getParent()).removeView(NotificationFooterLayout.this); } }); diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java index 997def277..cc81b1121 100644 --- a/src/com/android/launcher3/notification/NotificationItemView.java +++ b/src/com/android/launcher3/notification/NotificationItemView.java @@ -21,7 +21,6 @@ import android.app.Notification; import android.content.Context; import android.graphics.Rect; import android.support.annotation.Nullable; -import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; @@ -30,11 +29,12 @@ import android.widget.TextView; import com.android.launcher3.ItemInfo; import com.android.launcher3.R; -import com.android.launcher3.anim.PillHeightRevealOutlineProvider; +import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; import com.android.launcher3.graphics.IconPalette; import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider; import com.android.launcher3.popup.PopupItemView; import com.android.launcher3.userevent.nano.LauncherLogProto; +import com.android.launcher3.util.Themes; import java.util.List; @@ -87,9 +87,10 @@ public class NotificationItemView extends PopupItemView implements LogContainerP } public Animator animateHeightRemoval(int heightToRemove) { - final int newHeight = getHeight() - heightToRemove; - return new PillHeightRevealOutlineProvider(mPillRect, - getBackgroundRadius(), newHeight).createRevealAnimator(this, true /* isReversed */); + Rect endRect = new Rect(mPillRect); + endRect.bottom -= heightToRemove; + return new RoundedRectRevealOutlineProvider(getBackgroundRadius(), getBackgroundRadius(), + mPillRect, endRect, mRoundedCorners).createRevealAnimator(this, false); } public void updateHeader(int notificationCount, @Nullable IconPalette palette) { @@ -98,7 +99,7 @@ public class NotificationItemView extends PopupItemView implements LogContainerP if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) { mNotificationHeaderTextColor = IconPalette.resolveContrastColor(getContext(), palette.dominantColor, - getResources().getColor(R.color.popup_header_background_color)); + Themes.getAttrColor(getContext(), R.attr.popupColorPrimary)); } mHeaderCount.setTextColor(mNotificationHeaderTextColor); } @@ -163,13 +164,6 @@ public class NotificationItemView extends PopupItemView implements LogContainerP } @Override - public int getArrowColor(boolean isArrowAttachedToBottom) { - return ContextCompat.getColor(getContext(), isArrowAttachedToBottom - ? R.color.popup_background_color - : R.color.popup_header_background_color); - } - - @Override public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target, LauncherLogProto.Target targetParent) { target.itemType = LauncherLogProto.ItemType.NOTIFICATION; diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java index 0d6da77ee..9b8dd648f 100644 --- a/src/com/android/launcher3/notification/NotificationMainView.java +++ b/src/com/android/launcher3/notification/NotificationMainView.java @@ -87,11 +87,11 @@ public class NotificationMainView extends FrameLayout implements SwipeHelper.Cal CharSequence title = mNotificationInfo.title; CharSequence text = mNotificationInfo.text; if (!TextUtils.isEmpty(title) && !TextUtils.isEmpty(text)) { - mTitleView.setText(title); - mTextView.setText(text); + mTitleView.setText(title.toString()); + mTextView.setText(text.toString()); } else { mTitleView.setMaxLines(2); - mTitleView.setText(TextUtils.isEmpty(title) ? text : title); + mTitleView.setText(TextUtils.isEmpty(title) ? text.toString() : title.toString()); mTextView.setVisibility(GONE); } iconView.setBackground(mNotificationInfo.getIconForBackground(getContext(), diff --git a/src/com/android/launcher3/pageindicators/CaretDrawable.java b/src/com/android/launcher3/pageindicators/CaretDrawable.java index 0a00e24e9..5ade49728 100644 --- a/src/com/android/launcher3/pageindicators/CaretDrawable.java +++ b/src/com/android/launcher3/pageindicators/CaretDrawable.java @@ -22,12 +22,11 @@ import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; +import android.graphics.drawable.Drawable; import com.android.launcher3.R; import com.android.launcher3.util.Themes; -import android.graphics.drawable.Drawable; - public class CaretDrawable extends Drawable { public static final float PROGRESS_CARET_POINTING_UP = -1f; public static final float PROGRESS_CARET_POINTING_DOWN = 1f; @@ -39,6 +38,7 @@ public class CaretDrawable extends Drawable { private Paint mCaretPaint = new Paint(); private Path mPath = new Path(); private final int mCaretSizePx; + private final boolean mUseShadow; public CaretDrawable(Context context) { final Resources res = context.getResources(); @@ -46,12 +46,12 @@ public class CaretDrawable extends Drawable { final int strokeWidth = res.getDimensionPixelSize(R.dimen.all_apps_caret_stroke_width); final int shadowSpread = res.getDimensionPixelSize(R.dimen.all_apps_caret_shadow_spread); - mCaretPaint.setColor(res.getColor(R.color.workspace_icon_text_color)); + mCaretPaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor)); mCaretPaint.setAntiAlias(true); mCaretPaint.setStrokeWidth(strokeWidth); mCaretPaint.setStyle(Paint.Style.STROKE); - mCaretPaint.setStrokeCap(Paint.Cap.SQUARE); - mCaretPaint.setStrokeJoin(Paint.Join.MITER); + mCaretPaint.setStrokeCap(Paint.Cap.ROUND); + mCaretPaint.setStrokeJoin(Paint.Join.ROUND); mShadowPaint.setColor(res.getColor(R.color.default_shadow_color_no_alpha)); mShadowPaint.setAlpha(Themes.getAlpha(context, android.R.attr.spotShadowAlpha)); @@ -61,6 +61,7 @@ public class CaretDrawable extends Drawable { mShadowPaint.setStrokeCap(Paint.Cap.ROUND); mShadowPaint.setStrokeJoin(Paint.Join.ROUND); + mUseShadow = !Themes.getAttrBoolean(context, R.attr.isWorkspaceDarkText); mCaretSizePx = res.getDimensionPixelSize(R.dimen.all_apps_caret_size); } @@ -95,8 +96,9 @@ public class CaretDrawable extends Drawable { mPath.moveTo(left, top + caretHeight * (1 - getNormalizedCaretProgress())); mPath.lineTo(left + (width / 2), top + caretHeight * getNormalizedCaretProgress()); mPath.lineTo(left + width, top + caretHeight * (1 - getNormalizedCaretProgress())); - - canvas.drawPath(mPath, mShadowPaint); + if (mUseShadow) { + canvas.drawPath(mPath, mShadowPaint); + } canvas.drawPath(mPath, mCaretPaint); } diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java b/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java index aedf28384..ae10aedee 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java +++ b/src/com/android/launcher3/pageindicators/PageIndicatorCaretLandscape.java @@ -16,9 +16,7 @@ package com.android.launcher3.pageindicators; import android.content.Context; -import android.content.res.Resources; import android.graphics.Canvas; -import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java b/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java index 3ceba8419..91fc1f04a 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java +++ b/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java @@ -221,7 +221,7 @@ public class PageIndicatorLineCaret extends PageIndicator { */ public void updateColor(ExtractedColors extractedColors) { int originalLineAlpha = mLinePaint.getAlpha(); - int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX, Color.TRANSPARENT); + int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX); if (color != Color.TRANSPARENT) { color = ColorUtils.setAlphaComponent(color, 255); if (color == Color.BLACK) { diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 4488f6634..5463ef772 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -20,19 +20,21 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; import android.graphics.CornerPathEffect; +import android.graphics.Outline; import android.graphics.Paint; +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.IntDef; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; @@ -40,7 +42,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; -import android.view.animation.DecelerateInterpolator; +import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.FrameLayout; import com.android.launcher3.AbstractFloatingView; @@ -52,13 +54,13 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; -import com.android.launcher3.LogAccelerateInterpolator; import com.android.launcher3.R; 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.anim.RoundedRectRevealOutlineProvider; import com.android.launcher3.badge.BadgeInfo; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; @@ -71,7 +73,10 @@ import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.shortcuts.ShortcutsItemView; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.util.Themes; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.List; import java.util.Map; @@ -88,6 +93,16 @@ import static com.android.launcher3.userevent.nano.LauncherLogProto.Target; public class PopupContainerWithArrow extends AbstractFloatingView implements DragSource, DragController.DragListener { + public static final int ROUNDED_TOP_CORNERS = 1 << 0; + public static final int ROUNDED_BOTTOM_CORNERS = 1 << 1; + + @IntDef(flag = true, value = { + ROUNDED_TOP_CORNERS, + ROUNDED_BOTTOM_CORNERS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RoundedCornerFlags {} + protected final Launcher mLauncher; private final int mStartDragThreshold; private LauncherAccessibilityDelegate mAccessibilityDelegate; @@ -106,6 +121,8 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra protected Animator mOpenCloseAnimator; private boolean mDeferContainerRemoval; private AnimatorSet mReduceHeightAnimatorSet; + private final Rect mStartRect = new Rect(); + private final Rect mEndRect = new Rect(); public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); @@ -221,6 +238,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra mArrow.setPivotX(arrowWidth / 2); mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight); + measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); animateOpen(); mLauncher.getDragController().addDragListener(this); @@ -237,46 +255,67 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra private void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate, boolean notificationFooterHasIcons) { final Resources res = getResources(); - final int spacing = res.getDimensionPixelSize(R.dimen.popup_items_spacing); final LayoutInflater inflater = mLauncher.getLayoutInflater(); + int shortcutsItemRoundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS; int numItems = itemTypesToPopulate.length; for (int i = 0; i < numItems; i++) { PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i]; + PopupPopulator.Item prevItemTypeToPopulate = + i > 0 ? itemTypesToPopulate[i - 1] : null; PopupPopulator.Item nextItemTypeToPopulate = i < numItems - 1 ? itemTypesToPopulate[i + 1] : null; final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false); + boolean shouldUnroundTopCorners = prevItemTypeToPopulate != null + && itemTypeToPopulate.isShortcut ^ prevItemTypeToPopulate.isShortcut; + boolean shouldUnroundBottomCorners = nextItemTypeToPopulate != null + && itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut; + 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; + if (notificationFooterHasIcons) { + mNotificationItemView.findViewById(R.id.divider).setVisibility(VISIBLE); + } + + int roundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS; + if (shouldUnroundTopCorners) { + roundedCorners &= ~ROUNDED_TOP_CORNERS; + } + if (shouldUnroundBottomCorners) { + roundedCorners &= ~ROUNDED_BOTTOM_CORNERS; + } + int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorTertiary); + mNotificationItemView.setBackgroundWithCorners(backgroundColor, roundedCorners); + mNotificationItemView.getMainView().setAccessibilityDelegate(mAccessibilityDelegate); } else if (itemTypeToPopulate == PopupPopulator.Item.SHORTCUT) { item.setAccessibilityDelegate(mAccessibilityDelegate); } - boolean shouldAddBottomMargin = nextItemTypeToPopulate != null - && itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut; - if (itemTypeToPopulate.isShortcut) { if (mShortcutsItemView == null) { mShortcutsItemView = (ShortcutsItemView) inflater.inflate( R.layout.shortcuts_item, this, false); addView(mShortcutsItemView); + if (shouldUnroundTopCorners) { + shortcutsItemRoundedCorners &= ~ROUNDED_TOP_CORNERS; + } } mShortcutsItemView.addShortcutView(item, itemTypeToPopulate); - if (shouldAddBottomMargin) { - ((LayoutParams) mShortcutsItemView.getLayoutParams()).bottomMargin = spacing; + if (shouldUnroundBottomCorners) { + shortcutsItemRoundedCorners &= ~ROUNDED_BOTTOM_CORNERS; } } else { addView(item); - if (shouldAddBottomMargin) { - ((LayoutParams) item.getLayoutParams()).bottomMargin = spacing; - } } } + int backgroundColor = Themes.getAttrColor(mLauncher, mNotificationItemView == null + ? R.attr.popupColorPrimary : R.attr.popupColorSecondary); + mShortcutsItemView.setBackgroundWithCorners(backgroundColor, shortcutsItemRoundedCorners); } protected PopupItemView getItemViewAt(int index) { @@ -296,45 +335,31 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra setVisibility(View.VISIBLE); mIsOpen = true; - final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet(); - final int itemCount = getItemCount(); - - final long duration = getResources().getInteger( - R.integer.config_deepShortcutOpenDuration); - final long arrowScaleDuration = getResources().getInteger( - R.integer.config_deepShortcutArrowOpenDuration); - final long arrowScaleDelay = duration - arrowScaleDuration; - final long stagger = getResources().getInteger( - R.integer.config_deepShortcutOpenStagger); - final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0); - - // Animate shortcuts - DecelerateInterpolator interpolator = new DecelerateInterpolator(); - for (int i = 0; i < itemCount; i++) { - final PopupItemView popupItemView = getItemViewAt(i); - popupItemView.setVisibility(INVISIBLE); - popupItemView.setAlpha(0); - - Animator anim = popupItemView.createOpenAnimation(mIsAboveIcon, mIsLeftAligned); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - popupItemView.setVisibility(VISIBLE); - } - }); - anim.setDuration(duration); - int animationIndex = mIsAboveIcon ? itemCount - i - 1 : i; - anim.setStartDelay(stagger * animationIndex); - anim.setInterpolator(interpolator); - shortcutAnims.play(anim); - - Animator fadeAnim = ObjectAnimator.ofFloat(popupItemView, View.ALPHA, 1); - fadeAnim.setInterpolator(fadeInterpolator); - // We want the shortcut to be fully opaque before the arrow starts animating. - fadeAnim.setDuration(arrowScaleDelay); - shortcutAnims.play(fadeAnim); - } - shortcutAnims.addListener(new AnimatorListenerAdapter() { + final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet(); + final Resources res = getResources(); + + // Rectangular reveal. + int itemsTotalHeight = 0; + for (int i = 0; i < getItemCount(); i++) { + itemsTotalHeight += getItemViewAt(i).getMeasuredHeight(); + } + Point startPoint = computeAnimStartPoint(itemsTotalHeight); + int top = mIsAboveIcon ? getPaddingTop() : startPoint.y; + float radius = getItemViewAt(0).getBackgroundRadius(); + mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y); + mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight); + final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider + (radius, radius, mStartRect, mEndRect).createRevealAnimator(this, false); + revealAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration)); + revealAnim.setInterpolator(new AccelerateDecelerateInterpolator()); + + // Animate the arrow. + mArrow.setScaleX(0); + mArrow.setScaleY(0); + Animator arrowScale = createArrowScaleAnim(1).setDuration(res.getInteger( + R.integer.config_popupArrowOpenDuration)); + + openAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mOpenCloseAnimator = null; @@ -345,15 +370,26 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra } }); - // Animate the arrow - mArrow.setScaleX(0); - mArrow.setScaleY(0); - Animator arrowScale = createArrowScaleAnim(1).setDuration(arrowScaleDuration); - arrowScale.setStartDelay(arrowScaleDelay); - shortcutAnims.play(arrowScale); + mOpenCloseAnimator = openAnim; + openAnim.playSequentially(revealAnim, arrowScale); + openAnim.start(); + } - mOpenCloseAnimator = shortcutAnims; - shortcutAnims.start(); + /** + * Returns the point at which the center of the arrow merges with the first popup item. + */ + private Point computeAnimStartPoint(int itemsTotalHeight) { + int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ? + R.dimen.popup_arrow_horizontal_center_start: + R.dimen.popup_arrow_horizontal_center_end); + if (!mIsLeftAligned) { + arrowCenterX = getMeasuredWidth() - arrowCenterX; + } + int arrowHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() + - itemsTotalHeight; + // The y-coordinate of edge between the arrow and the first popup item. + int arrowEdge = getPaddingTop() + (mIsAboveIcon ? itemsTotalHeight : arrowHeight); + return new Point(arrowCenterX, arrowEdge); } /** @@ -505,7 +541,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra // since the latter expects the arrow which hasn't been added yet. PopupItemView itemAttachedToArrow = (PopupItemView) (getChildAt(mIsAboveIcon ? getChildCount() - 1 : 0)); - arrowPaint.setColor(itemAttachedToArrow.getArrowColor(mIsAboveIcon)); + arrowPaint.setColor(Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary)); // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable. int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius); arrowPaint.setPathEffect(new CornerPathEffect(radius)); @@ -529,6 +565,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra */ public DragOptions.PreDragCondition createPreDragCondition() { return new DragOptions.PreDragCondition() { + @Override public boolean shouldStartDrag(double distanceDragged) { return distanceDragged > mStartDragThreshold; @@ -536,15 +573,27 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra @Override public void onPreDragStart(DropTarget.DragObject dragObject) { - mOriginalIcon.setVisibility(INVISIBLE); + if (mIsAboveIcon) { + // Hide only the icon, keep the text visible. + mOriginalIcon.setIconVisible(false); + mOriginalIcon.setVisibility(VISIBLE); + } else { + // Hide both the icon and text. + mOriginalIcon.setVisibility(INVISIBLE); + } } @Override public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) { - if (!dragStarted) { - mOriginalIcon.setVisibility(VISIBLE); + mOriginalIcon.setIconVisible(true); + if (dragStarted) { + // Make sure we keep the original icon hidden while it is being dragged. + mOriginalIcon.setVisibility(INVISIBLE); + } else { mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mOriginalIcon); if (!mIsAboveIcon) { + // Show the icon but keep the text hidden. + mOriginalIcon.setVisibility(VISIBLE); mOriginalIcon.setTextVisibility(false); } } @@ -593,22 +642,8 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra AnimatorSet removeNotification = LauncherAnimUtils.createAnimatorSet(); final int duration = getResources().getInteger( R.integer.config_removeNotificationViewDuration); - final int spacing = getResources().getDimensionPixelSize(R.dimen.popup_items_spacing); removeNotification.play(reduceNotificationViewHeight( - mNotificationItemView.getHeightMinusFooter() + spacing, duration)); - final View removeMarginView = mIsAboveIcon ? getItemViewAt(getItemCount() - 2) - : mNotificationItemView; - if (removeMarginView != null) { - ValueAnimator removeMargin = ValueAnimator.ofFloat(1, 0).setDuration(duration); - removeMargin.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - ((MarginLayoutParams) removeMarginView.getLayoutParams()).bottomMargin - = (int) (spacing * (float) valueAnimator.getAnimatedValue()); - } - }); - removeNotification.play(removeMargin); - } + mNotificationItemView.getHeightMinusFooter(), duration)); Animator fade = ObjectAnimator.ofFloat(mNotificationItemView, ALPHA, 0) .setDuration(duration); fade.addListener(new AnimatorListenerAdapter() { @@ -618,19 +653,24 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra mNotificationItemView = null; if (getItemCount() == 0) { close(false); - return; } } }); removeNotification.play(fade); final long arrowScaleDuration = getResources().getInteger( - R.integer.config_deepShortcutArrowOpenDuration); + R.integer.config_popupArrowOpenDuration); Animator hideArrow = createArrowScaleAnim(0).setDuration(arrowScaleDuration); hideArrow.setStartDelay(0); Animator showArrow = createArrowScaleAnim(1).setDuration(arrowScaleDuration); showArrow.setStartDelay((long) (duration - arrowScaleDuration * 1.5)); removeNotification.playSequentially(hideArrow, showArrow); removeNotification.start(); + if (mShortcutsItemView != null) { + int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary); + // With notifications gone, all corners of shortcuts item should be rounded. + mShortcutsItemView.setBackgroundWithCorners(backgroundColor, + ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS); + } return; } mNotificationItemView.trimNotifications(NotificationKeyData.extractKeysOnly( @@ -754,55 +794,40 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra if (!mIsOpen) { return; } + mEndRect.setEmpty(); if (mOpenCloseAnimator != null) { + Outline outline = new Outline(); + getOutlineProvider().getOutline(this, outline); + outline.getRect(mEndRect); mOpenCloseAnimator.cancel(); } mIsOpen = false; - final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet(); - final int itemCount = getItemCount(); - int numOpenShortcuts = 0; - for (int i = 0; i < itemCount; i++) { - if (getItemViewAt(i).isOpenOrOpening()) { - numOpenShortcuts++; - } + final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet(); + final Resources res = getResources(); + + // Animate the arrow. + Animator arrowScale = createArrowScaleAnim(0).setDuration(res.getInteger( + R.integer.config_popupArrowOpenDuration)); + + // Rectangular reveal (reversed). + int itemsTotalHeight = 0; + for (int i = 0; i < getItemCount(); i++) { + itemsTotalHeight += getItemViewAt(i).getMeasuredHeight(); } - final long duration = getResources().getInteger( - R.integer.config_deepShortcutCloseDuration); - final long arrowScaleDuration = getResources().getInteger( - R.integer.config_deepShortcutArrowOpenDuration); - final long stagger = getResources().getInteger( - R.integer.config_deepShortcutCloseStagger); - final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0); - - int firstOpenItemIndex = mIsAboveIcon ? itemCount - numOpenShortcuts : 0; - for (int i = firstOpenItemIndex; i < firstOpenItemIndex + numOpenShortcuts; i++) { - final PopupItemView view = getItemViewAt(i); - Animator anim; - 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) { - view.setVisibility(INVISIBLE); - } - }); - shortcutAnims.play(anim); + Point startPoint = computeAnimStartPoint(itemsTotalHeight); + int top = mIsAboveIcon ? getPaddingTop() : startPoint.y; + float radius = getItemViewAt(0).getBackgroundRadius(); + mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y); + if (mEndRect.isEmpty()) { + mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight); } - Animator arrowAnim = createArrowScaleAnim(0).setDuration(arrowScaleDuration); - arrowAnim.setStartDelay(0); - shortcutAnims.play(arrowAnim); + final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider( + radius, radius, mStartRect, mEndRect).createRevealAnimator(this, true); + revealAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration)); + revealAnim.setInterpolator(new AccelerateDecelerateInterpolator()); - shortcutAnims.addListener(new AnimatorListenerAdapter() { + closeAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mOpenCloseAnimator = null; @@ -813,8 +838,9 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra } } }); - mOpenCloseAnimator = shortcutAnims; - shortcutAnims.start(); + mOpenCloseAnimator = closeAnim; + closeAnim.playSequentially(arrowScale, revealAnim); + closeAnim.start(); mOriginalIcon.forceHideBadge(false); } diff --git a/src/com/android/launcher3/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java index 384f554e1..05cadb671 100644 --- a/src/com/android/launcher3/popup/PopupItemView.java +++ b/src/com/android/launcher3/popup/PopupItemView.java @@ -16,38 +16,34 @@ package com.android.launcher3.popup; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; -import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.RoundRectShape; import android.util.AttributeSet; import android.view.View; 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.popup.PopupContainerWithArrow.RoundedCornerFlags; + +import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS; +import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS; /** - * An abstract {@link FrameLayout} that supports animating an item's content - * (e.g. icon and text) separate from the item's background. + * An abstract {@link FrameLayout} that contains content for {@link PopupContainerWithArrow}. */ -public abstract class PopupItemView extends FrameLayout - implements ValueAnimator.AnimatorUpdateListener { - - protected static final Point sTempPoint = new Point(); +public abstract class PopupItemView extends FrameLayout { protected final Rect mPillRect; - private float mOpenAnimationProgress; + protected @RoundedCornerFlags int mRoundedCorners; protected final boolean mIsRtl; protected View mIconView; @@ -93,164 +89,55 @@ public abstract class PopupItemView extends FrameLayout @Override protected void dispatchDraw(Canvas canvas) { + if (mRoundedCorners == 0) { + super.dispatchDraw(canvas); + return; + } + int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null); super.dispatchDraw(canvas); + // Clip children to this item's rounded corners. int cornerWidth = mRoundedCornerBitmap.getWidth(); int cornerHeight = mRoundedCornerBitmap.getHeight(); - // Clip top left corner. - mMatrix.reset(); - canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint); - // Clip top right corner. - mMatrix.setRotate(90, cornerWidth / 2, cornerHeight / 2); - mMatrix.postTranslate(canvas.getWidth() - cornerWidth, 0); - canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint); - // Clip bottom right corner. - mMatrix.setRotate(180, cornerWidth / 2, cornerHeight / 2); - mMatrix.postTranslate(canvas.getWidth() - cornerWidth, canvas.getHeight() - cornerHeight); - canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint); - // Clip bottom left corner. - mMatrix.setRotate(270, cornerWidth / 2, cornerHeight / 2); - mMatrix.postTranslate(0, canvas.getHeight() - cornerHeight); - canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint); + if ((mRoundedCorners & ROUNDED_TOP_CORNERS) != 0) { + // Clip top left corner. + mMatrix.reset(); + canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint); + // Clip top right corner. + mMatrix.setRotate(90, cornerWidth / 2, cornerHeight / 2); + mMatrix.postTranslate(canvas.getWidth() - cornerWidth, 0); + canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint); + } + if ((mRoundedCorners & ROUNDED_BOTTOM_CORNERS) != 0) { + // Clip bottom right corner. + mMatrix.setRotate(180, cornerWidth / 2, cornerHeight / 2); + mMatrix.postTranslate(canvas.getWidth() - cornerWidth, canvas.getHeight() - cornerHeight); + canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint); + // Clip bottom left corner. + mMatrix.setRotate(270, cornerWidth / 2, cornerHeight / 2); + mMatrix.postTranslate(0, canvas.getHeight() - cornerHeight); + canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint); + } canvas.restoreToCount(saveCount); } /** - * Creates an animator to play when the shortcut container is being opened. + * Creates a round rect drawable (with the specified corners unrounded) + * and sets it as this View's background. */ - public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) { - Point center = getIconCenter(); - int arrowCenter = getResources().getDimensionPixelSize(pivotLeft ^ mIsRtl ? - R.dimen.popup_arrow_horizontal_center_start: - R.dimen.popup_arrow_horizontal_center_end); - ValueAnimator openAnimator = new ZoomRevealOutlineProvider(center.x, center.y, - mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft, arrowCenter) - .createRevealAnimator(this, false); - mOpenAnimationProgress = 0f; - openAnimator.addUpdateListener(this); - return openAnimator; - } - - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - mOpenAnimationProgress = valueAnimator.getAnimatedFraction(); - } - - public boolean isOpenOrOpening() { - return mOpenAnimationProgress > 0; - } - - /** - * Creates an animator to play when the shortcut container is being closed. - */ - public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft, - long duration) { - Point center = getIconCenter(); - int arrowCenter = getResources().getDimensionPixelSize(pivotLeft ^ mIsRtl ? - R.dimen.popup_arrow_horizontal_center_start : - R.dimen.popup_arrow_horizontal_center_end); - ValueAnimator closeAnimator = new ZoomRevealOutlineProvider(center.x, center.y, - mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft, arrowCenter) - .createRevealAnimator(this, true); - // Scale down the duration and interpolator according to the progress - // that the open animation was at when the close started. - closeAnimator.setDuration((long) (duration * mOpenAnimationProgress)); - closeAnimator.setInterpolator(new CloseInterpolator(mOpenAnimationProgress)); - closeAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mOpenAnimationProgress = 0; - } - }); - return closeAnimator; - } - - /** - * Returns the position of the center of the icon relative to the container. - */ - public Point getIconCenter() { - sTempPoint.y = getMeasuredHeight() / 2; - sTempPoint.x = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height) / 2; - if (Utilities.isRtl(getResources())) { - sTempPoint.x = getMeasuredWidth() - sTempPoint.x; - } - return sTempPoint; + public void setBackgroundWithCorners(int color, @RoundedCornerFlags int roundedCorners) { + mRoundedCorners = roundedCorners; + float rTop = (roundedCorners & ROUNDED_TOP_CORNERS) == 0 ? 0 : getBackgroundRadius(); + float rBot = (roundedCorners & ROUNDED_BOTTOM_CORNERS) == 0 ? 0 : getBackgroundRadius(); + float[] radii = new float[] {rTop, rTop, rTop, rTop, rBot, rBot, rBot, rBot}; + ShapeDrawable roundRectBackground = new ShapeDrawable(new RoundRectShape(radii, null, null)); + roundRectBackground.getPaint().setColor(color); + setBackground(roundRectBackground); } protected float getBackgroundRadius() { return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius); } - - public abstract int getArrowColor(boolean isArrowAttachedToBottom); - - /** - * Extension of {@link PillRevealOutlineProvider} which scales the icon based on the height. - */ - private static class ZoomRevealOutlineProvider extends PillRevealOutlineProvider { - - private final View mTranslateView; - private final View mZoomView; - - private final float mFullHeight; - private final float mTranslateYMultiplier; - - private final boolean mPivotLeft; - private final float mTranslateX; - private final float mArrowCenter; - - public ZoomRevealOutlineProvider(int x, int y, Rect pillRect, PopupItemView translateView, - View zoomView, boolean isContainerAboveIcon, boolean pivotLeft, float arrowCenter) { - super(x, y, pillRect, translateView.getBackgroundRadius()); - mTranslateView = translateView; - mZoomView = zoomView; - mFullHeight = pillRect.height(); - - mTranslateYMultiplier = isContainerAboveIcon ? 0.5f : -0.5f; - - mPivotLeft = pivotLeft; - mTranslateX = pivotLeft ? arrowCenter : pillRect.right - arrowCenter; - mArrowCenter = arrowCenter; - } - - @Override - public void setProgress(float progress) { - super.setProgress(progress); - - if (mZoomView != null) { - mZoomView.setScaleX(progress); - mZoomView.setScaleY(progress); - } - - float height = mOutline.height(); - mTranslateView.setTranslationY(mTranslateYMultiplier * (mFullHeight - height)); - - float offsetX = Math.min(mOutline.width(), mArrowCenter); - float pivotX = mPivotLeft ? (mOutline.left + offsetX) : (mOutline.right - offsetX); - mTranslateView.setTranslationX(mTranslateX - pivotX); - } - } - - /** - * An interpolator that reverses the current open animation progress. - */ - private static class CloseInterpolator extends LogAccelerateInterpolator { - private float mStartProgress; - private float mRemainingProgress; - - /** - * @param openAnimationProgress The progress that the open interpolator ended at. - */ - public CloseInterpolator(float openAnimationProgress) { - super(100, 0); - mStartProgress = 1f - openAnimationProgress; - mRemainingProgress = openAnimationProgress; - } - - @Override - public float getInterpolation(float v) { - return mStartProgress + super.getInterpolation(v) * mRemainingProgress; - } - } } diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java index c62d8771a..fd0010599 100644 --- a/src/com/android/launcher3/popup/PopupPopulator.java +++ b/src/com/android/launcher3/popup/PopupPopulator.java @@ -305,14 +305,12 @@ public class PopupPopulator { if (view instanceof DeepShortcutView) { // Expanded system shortcut, with both icon and text shown on white background. final DeepShortcutView shortcutView = (DeepShortcutView) view; - shortcutView.getIconView().setBackground(info.getIcon(context, - android.R.attr.textColorTertiary)); + shortcutView.getIconView().setBackground(info.getIcon(context)); shortcutView.getBubbleText().setText(info.getLabel(context)); } else if (view instanceof ImageView) { // Only the system shortcut icon shows on a gray background header. final ImageView shortcutIcon = (ImageView) view; - shortcutIcon.setImageDrawable(info.getIcon(context, - android.R.attr.textColorHint)); + shortcutIcon.setImageDrawable(info.getIcon(context)); shortcutIcon.setContentDescription(info.getLabel(context)); } view.setTag(info); diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java index f158f7185..6254d2d00 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -13,18 +13,20 @@ import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.Themes; import com.android.launcher3.widget.WidgetsBottomSheet; import java.util.List; +import static com.android.launcher3.userevent.nano.LauncherLogProto.Action; +import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; + /** * Represents a system shortcut for a given app. The shortcut should have a static label and * icon, and an onClickListener that depends on the item that the shortcut services. * * Example system shortcuts, defined as inner classes, include Widgets and AppInfo. */ -public abstract class SystemShortcut { +public abstract class SystemShortcut extends ItemInfo { private final int mIconResId; private final int mLabelResId; @@ -33,10 +35,8 @@ public abstract class SystemShortcut { mLabelResId = labelResId; } - public Drawable getIcon(Context context, int colorAttr) { - Drawable icon = context.getResources().getDrawable(mIconResId, context.getTheme()).mutate(); - icon.setTint(Themes.getAttrColor(context, colorAttr)); - return icon; + public Drawable getIcon(Context context) { + return context.getResources().getDrawable(mIconResId, context.getTheme()); } public String getLabel(Context context) { @@ -68,6 +68,8 @@ public abstract class SystemShortcut { (WidgetsBottomSheet) launcher.getLayoutInflater().inflate( R.layout.widgets_bottom_sheet, launcher.getDragLayer(), false); widgetsBottomSheet.populateAndShow(itemInfo); + launcher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, + ControlType.WIDGETS_BUTTON, view); } }; } @@ -87,6 +89,8 @@ public abstract class SystemShortcut { Rect sourceBounds = launcher.getViewBounds(view); Bundle opts = launcher.getActivityLaunchOptions(view); InfoDropTarget.startDetailsActivityForInfo(itemInfo, launcher, null, sourceBounds, opts); + launcher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, + ControlType.APPINFO_TARGET, view); } }; } diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java index b0482f8b2..314f24417 100644 --- a/src/com/android/launcher3/provider/ImportDataTask.java +++ b/src/com/android/launcher3/provider/ImportDataTask.java @@ -16,6 +16,8 @@ package com.android.launcher3.provider; +import static com.android.launcher3.Utilities.getDevicePrefs; + import android.content.ContentProviderOperation; import android.content.ContentValues; import android.content.Context; @@ -36,7 +38,7 @@ import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback; 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 +48,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 +113,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 +290,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 +301,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 +320,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); } } @@ -377,10 +378,6 @@ public class ImportDataTask { return false; } - private static SharedPreferences getDevicePrefs(Context c) { - return c.getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE); - } - private static final int getMyHotseatLayoutId(Context context) { return LauncherAppState.getIDP(context).numHotseatIcons <= 5 ? R.xml.dw_phone_hotseat diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java index 175835011..74373d307 100644 --- a/src/com/android/launcher3/provider/LauncherDbUtils.java +++ b/src/com/android/launcher3/provider/LauncherDbUtils.java @@ -19,15 +19,16 @@ package com.android.launcher3.provider; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.util.Log; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherSettings.WorkspaceScreens; -import com.android.launcher3.logging.FileLog; import java.util.ArrayList; +import java.util.Collection; /** * A set of utility methods for Launcher DB used for DB updates and migration. @@ -44,14 +45,14 @@ public class LauncherDbUtils { * items are simply deleted. */ public static boolean prepareScreenZeroToHostQsb(Context context, SQLiteDatabase db) { - db.beginTransaction(); - try { + try (SQLiteTransaction t = new SQLiteTransaction(db)) { // Get the existing screens ArrayList<Long> screenIds = getScreenIdsFromCursor(db.query(WorkspaceScreens.TABLE_NAME, null, null, null, null, null, WorkspaceScreens.SCREEN_RANK)); if (screenIds.isEmpty()) { // No update needed + t.commit(); return true; } if (screenIds.get(0) != 0) { @@ -68,23 +69,20 @@ public class LauncherDbUtils { } // Check if the first row is empty - try (Cursor c = db.query(Favorites.TABLE_NAME, null, - "container = -100 and screen = 0 and cellY = 0", null, null, null, null)) { - if (c.getCount() == 0) { - // First row is empty, no need to migrate. - return true; - } + if (DatabaseUtils.queryNumEntries(db, Favorites.TABLE_NAME, + "container = -100 and screen = 0 and cellY = 0") == 0) { + // First row is empty, no need to migrate. + t.commit(); + return true; } new LossyScreenMigrationTask(context, LauncherAppState.getIDP(context), db) .migrateScreen0(); - db.setTransactionSuccessful(); + t.commit(); return true; } catch (Exception e) { Log.e(TAG, "Failed to update workspace size", e); return false; - } finally { - db.endTransaction(); } } @@ -104,19 +102,40 @@ public class LauncherDbUtils { * Parses the cursor containing workspace screens table and returns the list of screen IDs */ public static ArrayList<Long> getScreenIdsFromCursor(Cursor sc) { - ArrayList<Long> screenIds = new ArrayList<Long>(); try { - final int idIndex = sc.getColumnIndexOrThrow(WorkspaceScreens._ID); - while (sc.moveToNext()) { - try { - screenIds.add(sc.getLong(idIndex)); - } catch (Exception e) { - FileLog.d(TAG, "Invalid screen id", e); - } - } + return iterateCursor(sc, + sc.getColumnIndexOrThrow(WorkspaceScreens._ID), + new ArrayList<Long>()); } finally { sc.close(); } - return screenIds; + } + + public static <T extends Collection<Long>> T iterateCursor(Cursor c, int columnIndex, T out) { + while (c.moveToNext()) { + out.add(c.getLong(columnIndex)); + } + return out; + } + + /** + * Utility class to simplify managing sqlite transactions + */ + public static class SQLiteTransaction implements AutoCloseable { + private final SQLiteDatabase mDb; + + public SQLiteTransaction(SQLiteDatabase db) { + mDb = db; + db.beginTransaction(); + } + + public void commit() { + mDb.setTransactionSuccessful(); + } + + @Override + public void close() { + mDb.endTransaction(); + } } } diff --git a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java index 4addbfa23..51890d194 100644 --- a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java +++ b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java @@ -21,7 +21,6 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.graphics.Point; -import android.util.Log; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherSettings.Favorites; @@ -31,7 +30,6 @@ import com.android.launcher3.model.GridSizeMigrationTask; import com.android.launcher3.util.LongArrayMap; import java.util.ArrayList; -import java.util.HashMap; /** * An extension of {@link GridSizeMigrationTask} which migrates only one screen and diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java index dc85abad7..00e2644a5 100644 --- a/src/com/android/launcher3/provider/RestoreDbTask.java +++ b/src/com/android/launcher3/provider/RestoreDbTask.java @@ -27,6 +27,7 @@ import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.logging.FileLog; +import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; import com.android.launcher3.util.LogConfig; import java.io.InvalidObjectException; @@ -47,16 +48,13 @@ public class RestoreDbTask { public static boolean performRestore(DatabaseHelper helper) { SQLiteDatabase db = helper.getWritableDatabase(); - db.beginTransaction(); - try { + try (SQLiteTransaction t = new SQLiteTransaction(db)) { new RestoreDbTask().sanitizeDB(helper, db); - db.setTransactionSuccessful(); + t.commit(); return true; } catch (Exception e) { FileLog.e(TAG, "Failed to verify db", e); return false; - } finally { - db.endTransaction(); } } diff --git a/src/com/android/launcher3/qsb/QsbBlockerView.java b/src/com/android/launcher3/qsb/QsbBlockerView.java deleted file mode 100644 index 5379336de..000000000 --- a/src/com/android/launcher3/qsb/QsbBlockerView.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.qsb; - -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.view.View; - -import com.android.launcher3.Launcher; -import com.android.launcher3.Workspace; -import com.android.launcher3.Workspace.OnStateChangeListener; -import com.android.launcher3.Workspace.State; - -/** - * A simple view used to show the region blocked by QSB during drag and drop. - */ -public class QsbBlockerView extends View implements OnStateChangeListener { - - private static final int VISIBLE_ALPHA = 100; - - private final Paint mBgPaint; - - public QsbBlockerView(Context context, AttributeSet attrs) { - super(context, attrs); - - mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mBgPaint.setColor(Color.WHITE); - mBgPaint.setAlpha(0); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - Workspace w = Launcher.getLauncher(getContext()).getWorkspace(); - w.setOnStateChangeListener(this); - prepareStateChange(w.getState(), null); - } - - @Override - public void prepareStateChange(State toState, AnimatorSet targetAnim) { - int finalAlpha = getAlphaForState(toState); - if (targetAnim == null) { - mBgPaint.setAlpha(finalAlpha); - invalidate(); - } else { - ObjectAnimator anim = ObjectAnimator.ofArgb(mBgPaint, "alpha", finalAlpha); - anim.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - invalidate(); - } - }); - targetAnim.play(anim); - } - } - - private static int getAlphaForState(State state) { - switch (state) { - case SPRING_LOADED: - case OVERVIEW: - case OVERVIEW_HIDDEN: - return VISIBLE_ALPHA; - } - return 0; - } - - @Override - protected void onDraw(Canvas canvas) { - canvas.drawPaint(mBgPaint); - } -} diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java index df7f6954d..5ce78dc3a 100644 --- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java +++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java @@ -65,8 +65,10 @@ public class DeepShortcutManager { } public static boolean supportsShortcuts(ItemInfo info) { + boolean isItemPromise = info instanceof com.android.launcher3.ShortcutInfo + && ((com.android.launcher3.ShortcutInfo) info).isPromise(); return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION - && !info.isDisabled(); + && !info.isDisabled() && !isItemPromise; } public boolean wasLastCallSuccess() { diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java index ab8de6bd5..e9d2b50ea 100644 --- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java +++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java @@ -23,10 +23,10 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.View; -import com.android.launcher3.graphics.HolographicOutlineHelper; import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; import com.android.launcher3.graphics.DragPreviewProvider; +import com.android.launcher3.graphics.HolographicOutlineHelper; /** * Extension of {@link DragPreviewProvider} which generates bitmaps scaled to the default icon size. diff --git a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java index 37047bb36..9c91c87b2 100644 --- a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java +++ b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java @@ -18,15 +18,11 @@ package com.android.launcher3.shortcuts; import android.annotation.TargetApi; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutInfo; import android.os.Build; import android.os.UserHandle; -import com.android.launcher3.ItemInfo; -import com.android.launcher3.compat.UserManagerCompat; - /** * Wrapper class for {@link android.content.pm.ShortcutInfo}, representing deep shortcuts into apps. * diff --git a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java index 5b3b02eac..340a6f000 100644 --- a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java +++ b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java @@ -16,12 +16,10 @@ package com.android.launcher3.shortcuts; -import android.animation.Animator; -import android.animation.AnimatorSet; import android.content.Context; import android.graphics.Point; -import android.support.v4.content.ContextCompat; import android.util.AttributeSet; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; @@ -30,9 +28,7 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; 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; @@ -135,7 +131,17 @@ public class ShortcutsItemView extends PopupItemView implements View.OnLongClick if (mSystemShortcutIcons == null) { mSystemShortcutIcons = (LinearLayout) mLauncher.getLayoutInflater().inflate( R.layout.system_shortcut_icons, mShortcutsLayout, false); - mShortcutsLayout.addView(mSystemShortcutIcons, 0); + + View divider = LayoutInflater.from(getContext()).inflate( + R.layout.horizontal_divider, this, false); + + if (mShortcutsLayout.getChildCount() > 0) { + mShortcutsLayout.addView(divider); + } + mShortcutsLayout.addView(mSystemShortcutIcons); + if (mShortcutsLayout.getChildCount() == 1) { + mShortcutsLayout.addView(divider); + } } mSystemShortcutIcons.addView(shortcutView, index); } else { @@ -210,51 +216,6 @@ public class ShortcutsItemView extends PopupItemView implements View.OnLongClick } @Override - public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) { - AnimatorSet openAnimation = LauncherAnimUtils.createAnimatorSet(); - openAnimation.play(super.createOpenAnimation(isContainerAboveIcon, pivotLeft)); - for (int i = 0; i < mShortcutsLayout.getChildCount(); i++) { - if (!(mShortcutsLayout.getChildAt(i) instanceof DeepShortcutView)) { - continue; - } - DeepShortcutView shortcutView = ((DeepShortcutView) mShortcutsLayout.getChildAt(i)); - View deepShortcutIcon = shortcutView.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 < mShortcutsLayout.getChildCount(); i++) { - if (!(mShortcutsLayout.getChildAt(i) instanceof DeepShortcutView)) { - continue; - } - DeepShortcutView shortcutView = ((DeepShortcutView) mShortcutsLayout.getChildAt(i)); - View deepShortcutIcon = shortcutView.getIconView(); - deepShortcutIcon.setScaleX(1); - deepShortcutIcon.setScaleY(1); - closeAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder( - deepShortcutIcon, new PropertyListBuilder().scale(0).build())); - } - return closeAnimation; - } - - @Override - public int getArrowColor(boolean isArrowAttachedToBottom) { - return ContextCompat.getColor(getContext(), - isArrowAttachedToBottom || mSystemShortcutIcons == null - ? R.color.popup_background_color - : R.color.popup_header_background_color); - } - - @Override public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target, LauncherLogProto.Target targetParent) { target.itemType = LauncherLogProto.ItemType.DEEPSHORTCUT; diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java index aedca8db4..8d4351884 100644 --- a/src/com/android/launcher3/testing/LauncherExtension.java +++ b/src/com/android/launcher3/testing/LauncherExtension.java @@ -2,7 +2,6 @@ package com.android.launcher3.testing; import android.content.Intent; import android.graphics.Color; -import android.graphics.Rect; import android.os.Bundle; import android.view.Menu; import android.view.View; @@ -11,8 +10,6 @@ import android.widget.FrameLayout; import com.android.launcher3.AppInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherCallbacks; -import com.android.launcher3.allapps.AllAppsSearchBarController; -import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.util.ComponentKey; import java.io.FileDescriptor; @@ -200,11 +197,6 @@ public class LauncherExtension extends Launcher { } @Override - public AllAppsSearchBarController getAllAppsSearchBarController() { - return null; - } - - @Override public List<ComponentKey> getPredictedApps() { // To debug app predictions, enable AlphabeticalAppsList#DEBUG_PREDICTIONS return new ArrayList<>(); @@ -216,11 +208,6 @@ public class LauncherExtension extends Launcher { } @Override - public void setLauncherSearchCallback(Object callbacks) { - // Do nothing - } - - @Override public void onAttachedToWindow() { } diff --git a/src/com/android/launcher3/util/CachedPackageTracker.java b/src/com/android/launcher3/util/CachedPackageTracker.java deleted file mode 100644 index 314b4c0ed..000000000 --- a/src/com/android/launcher3/util/CachedPackageTracker.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * 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.util; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.LauncherActivityInfo; -import android.os.UserHandle; - -import com.android.launcher3.Utilities; -import com.android.launcher3.compat.LauncherAppsCompat; -import com.android.launcher3.compat.LauncherAppsCompat.OnAppsChangedCallbackCompat; -import com.android.launcher3.compat.UserManagerCompat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Utility class to track list of installed packages. It persists the list so that apps - * installed/uninstalled while Launcher was dead can also be handled properly. - */ -public abstract class CachedPackageTracker implements OnAppsChangedCallbackCompat { - - protected static final String INSTALLED_PACKAGES_PREFIX = "installed_packages_for_user_"; - - protected final SharedPreferences mPrefs; - protected final UserManagerCompat mUserManager; - protected final LauncherAppsCompat mLauncherApps; - - public CachedPackageTracker(Context context, String preferenceFileName) { - mPrefs = context.getSharedPreferences(preferenceFileName, Context.MODE_PRIVATE); - mUserManager = UserManagerCompat.getInstance(context); - mLauncherApps = LauncherAppsCompat.getInstance(context); - } - - /** - * Checks the list of user apps, and generates package event accordingly. - * {@see #onLauncherAppsAdded}, {@see #onLauncherPackageRemoved} - */ - public void processUserApps(List<LauncherActivityInfo> apps, UserHandle user) { - String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user); - HashSet<String> oldPackageSet = new HashSet<>(); - final boolean userAppsExisted = getUserApps(oldPackageSet, prefKey); - - HashSet<String> packagesRemoved = new HashSet<>(oldPackageSet); - HashSet<String> newPackageSet = new HashSet<>(); - ArrayList<LauncherActivityInstallInfo> packagesAdded = new ArrayList<>(); - - for (LauncherActivityInfo info : apps) { - String packageName = info.getComponentName().getPackageName(); - newPackageSet.add(packageName); - packagesRemoved.remove(packageName); - - if (!oldPackageSet.contains(packageName)) { - oldPackageSet.add(packageName); - packagesAdded.add(new LauncherActivityInstallInfo( - info, info.getFirstInstallTime())); - } - } - - if (!packagesAdded.isEmpty() || !packagesRemoved.isEmpty()) { - mPrefs.edit().putStringSet(prefKey, newPackageSet).apply(); - - if (!packagesAdded.isEmpty()) { - Collections.sort(packagesAdded); - onLauncherAppsAdded(packagesAdded, user, userAppsExisted); - } - - if (!packagesRemoved.isEmpty()) { - for (String pkg : packagesRemoved) { - onLauncherPackageRemoved(pkg, user); - } - } - } - } - - /** - * Reads the list of user apps which have already been processed. - * @return false if the list didn't exist, true otherwise - */ - private boolean getUserApps(HashSet<String> outExistingApps, String prefKey) { - Set<String> userApps = mPrefs.getStringSet(prefKey, null); - if (userApps == null) { - return false; - } else { - outExistingApps.addAll(userApps); - return true; - } - } - - @Override - public void onPackageRemoved(String packageName, UserHandle user) { - String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user); - HashSet<String> packageSet = new HashSet<>(); - if (getUserApps(packageSet, prefKey) && packageSet.remove(packageName)) { - mPrefs.edit().putStringSet(prefKey, packageSet).apply(); - } - - onLauncherPackageRemoved(packageName, user); - } - - @Override - public void onPackageAdded(String packageName, UserHandle user) { - String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user); - HashSet<String> packageSet = new HashSet<>(); - final boolean userAppsExisted = getUserApps(packageSet, prefKey); - if (!packageSet.contains(packageName)) { - List<LauncherActivityInfo> activities = - mLauncherApps.getActivityList(packageName, user); - if (!activities.isEmpty()) { - LauncherActivityInfo activityInfo = activities.get(0); - - packageSet.add(packageName); - mPrefs.edit().putStringSet(prefKey, packageSet).apply(); - onLauncherAppsAdded(Arrays.asList( - new LauncherActivityInstallInfo(activityInfo, System.currentTimeMillis())), - user, userAppsExisted); - } - } - } - - @Override - public void onPackageChanged(String packageName, UserHandle user) { } - - @Override - public void onPackagesAvailable( - String[] packageNames, UserHandle user, boolean replacing) { } - - @Override - public void onPackagesUnavailable( - String[] packageNames, UserHandle user, boolean replacing) { } - - @Override - public void onPackagesSuspended(String[] packageNames, UserHandle user) { } - - @Override - public void onPackagesUnsuspended(String[] packageNames, UserHandle user) { } - - /** - * Called when new launcher apps are added. - * @param apps list of newly added activities. Only one entry per package is sent. - * @param user the user for this event. All activities in {@param apps} will belong to - * the same user. - * @param userAppsExisted false if the list was processed for the first time, like in case - * when Launcher was newly installed or a new user was added. - */ - protected abstract void onLauncherAppsAdded(List<LauncherActivityInstallInfo> apps, - UserHandle user, boolean userAppsExisted); - - /** - * Called when apps are removed from the system. - */ - protected abstract void onLauncherPackageRemoved(String packageName, UserHandle user); - - public static class LauncherActivityInstallInfo - implements Comparable<LauncherActivityInstallInfo> { - public final LauncherActivityInfo info; - public final long installTime; - - public LauncherActivityInstallInfo(LauncherActivityInfo info, long installTime) { - this.info = info; - this.installTime = installTime; - } - - @Override - public int compareTo(LauncherActivityInstallInfo another) { - return Utilities.longCompare(installTime, another.installTime); - } - } -} diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java index afc45fe35..b80e94d15 100644 --- a/src/com/android/launcher3/util/FocusLogic.java +++ b/src/com/android/launcher3/util/FocusLogic.java @@ -23,7 +23,6 @@ import android.view.ViewGroup; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.config.FeatureFlags; diff --git a/src/com/android/launcher3/util/IOUtils.java b/src/com/android/launcher3/util/IOUtils.java new file mode 100644 index 000000000..77c21fe92 --- /dev/null +++ b/src/com/android/launcher3/util/IOUtils.java @@ -0,0 +1,55 @@ +/* + * 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 java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Supports various IO utility functions + */ +public class IOUtils { + + private static final int BUF_SIZE = 0x1000; // 4K + + public static byte[] toByteArray(File file) throws IOException { + try (InputStream in = new FileInputStream(file)) { + return toByteArray(in); + } + } + + public static byte[] toByteArray(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + copy(in, out); + return out.toByteArray(); + } + + public static long copy(InputStream from, OutputStream to) throws IOException { + byte[] buf = new byte[BUF_SIZE]; + long total = 0; + int r; + while ((r = from.read(buf)) != -1) { + to.write(buf, 0, r); + total += r; + } + return total; + } +} 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 ce603c4c2..091dd84bc 100644 --- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java +++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java @@ -19,23 +19,23 @@ package com.android.launcher3.util; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.LauncherActivityInfo; +import android.os.Handler; import android.os.Process; import android.os.UserHandle; -import android.support.v4.os.BuildCompat; -import com.android.launcher3.AppInfo; import com.android.launcher3.FolderInfo; -import com.android.launcher3.IconCache; +import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.ItemInfo; -import com.android.launcher3.LauncherAppState; 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.Utilities; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.shortcuts.ShortcutInfoCompat; +import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.model.ModelWriter; import java.util.ArrayList; import java.util.HashSet; @@ -47,11 +47,6 @@ import java.util.List; */ public class ManagedProfileHeuristic { - /** - * Maintain a set of packages installed per user. - */ - private static final String INSTALLED_PACKAGES_PREFIX = "installed_packages_for_user_"; - private static final String USER_FOLDER_ID_PREFIX = "user_folder_"; /** @@ -59,165 +54,154 @@ public class ManagedProfileHeuristic { */ private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000; - public static ManagedProfileHeuristic get(Context context, UserHandle user) { - if (!Process.myUserHandle().equals(user)) { - return new ManagedProfileHeuristic(context, user); + public static void onAllAppsLoaded(final Context context, + List<LauncherActivityInfo> apps, UserHandle user) { + if (Process.myUserHandle().equals(user)) { + return; } - return null; - } - private final Context mContext; - 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); - } + UserFolderInfo ufi = new UserFolderInfo(context, user, null); + // We only handle folder creation once. Later icon additions are handled using package + // or session events. + if (ufi.folderAlreadyCreated) { + return; + } - public void processPackageRemoved(String[] packages) { - Preconditions.assertWorkerThread(); - ManagedProfilePackageHandler handler = new ManagedProfilePackageHandler(); - for (String pkg : packages) { - handler.onPackageRemoved(pkg, mUser); + if (Utilities.isAtLeastO() && !SessionCommitReceiver.isEnabled(context)) { + // Just mark the folder id preference to avoid new folder creation later. + ufi.prefs.edit().putLong(ufi.folderIdKey, ItemInfo.NO_ID).apply(); + return; } - } - public void processPackageAdd(String[] packages) { - Preconditions.assertWorkerThread(); - ManagedProfilePackageHandler handler = new ManagedProfilePackageHandler(); - for (String pkg : packages) { - handler.onPackageAdded(pkg, mUser); + InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_BULK_ADD); + for (LauncherActivityInfo app : apps) { + // Queue all items which should go in the work folder. + if (app.getFirstInstallTime() < ufi.addIconToFolderTime) { + InstallShortcutReceiver.queueActivityInfo(app, context); + } } + // Post the queue update on next frame, so that the loader gets finished. + new Handler(LauncherModel.getWorkerLooper()).post(new Runnable() { + @Override + public void run() { + InstallShortcutReceiver.disableAndFlushInstallQueue( + InstallShortcutReceiver.FLAG_BULK_ADD, context); + } + }); } - public void processUserApps(List<LauncherActivityInfo> apps) { - Preconditions.assertWorkerThread(); - new ManagedProfilePackageHandler().processUserApps(apps, mUser); - } - private class ManagedProfilePackageHandler extends CachedPackageTracker { + /** + * Utility class to help workspace icon addition. + */ + public static class UserFolderInfo { - private ManagedProfilePackageHandler() { - super(mContext, LauncherFiles.MANAGED_USER_PREFERENCES_KEY); - } + final ArrayList<ShortcutInfo> pendingShortcuts = new ArrayList<>(); - protected void onLauncherAppsAdded( - List<LauncherActivityInstallInfo> apps, UserHandle user, boolean userAppsExisted) { - ArrayList<ShortcutInfo> workFolderApps = new ArrayList<>(); - ArrayList<ShortcutInfo> homescreenApps = new ArrayList<>(); - - int count = apps.size(); - long folderCreationTime = - mUserManager.getUserCreationTime(user) + AUTO_ADD_TO_FOLDER_DURATION; - - boolean quietModeEnabled = UserManagerCompat.getInstance(mContext) - .isQuietModeEnabled(user); - for (int i = 0; i < count; i++) { - LauncherActivityInstallInfo info = apps.get(i); - AppInfo appInfo = new AppInfo(info.info, user, quietModeEnabled); - mIconCache.getTitleAndIcon(appInfo, info.info, false /* useLowResIcon */); - ShortcutInfo si = appInfo.makeShortcut(); - ((info.installTime <= folderCreationTime) ? workFolderApps : homescreenApps).add(si); - } + final UserHandle user; - finalizeWorkFolder(user, workFolderApps, homescreenApps); + final long userSerial; + // Time until which icons will be added to folder instead. + final long addIconToFolderTime; - // 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() && mAddIconsToHomescreen) { - mModel.addAndBindAddedWorkspaceItems(new ArrayList<ItemInfo>(homescreenApps)); - } - } + final String folderIdKey; + final SharedPreferences prefs; + + final boolean folderAlreadyCreated; + final FolderInfo folderInfo; + + boolean folderPendingAddition; + + public UserFolderInfo(Context context, UserHandle user, BgDataModel dataModel) { + this.user = user; + + UserManagerCompat um = UserManagerCompat.getInstance(context); + userSerial = um.getSerialNumberForUser(user); + addIconToFolderTime = um.getUserCreationTime(user) + AUTO_ADD_TO_FOLDER_DURATION; - @Override - protected void onLauncherPackageRemoved(String packageName, UserHandle user) { + folderIdKey = USER_FOLDER_ID_PREFIX + userSerial; + prefs = prefs(context); + + folderAlreadyCreated = prefs.contains(folderIdKey); + if (dataModel != null) { + if (folderAlreadyCreated) { + long folderId = prefs.getLong(folderIdKey, ItemInfo.NO_ID); + folderInfo = dataModel.folders.get(folderId); + } else { + folderInfo = new FolderInfo(); + folderInfo.title = context.getText(R.string.work_folder_name); + folderInfo.setOption(FolderInfo.FLAG_WORK_FOLDER, true, null); + folderPendingAddition = true; + } + } else { + folderInfo = null; + } } /** - * Adds and binds shortcuts marked to be added to the work folder. + * Returns the ItemInfo which should be added to the workspace. In case the the provided + * {@link ShortcutInfo} or a wrapped {@link FolderInfo} or null. */ - private void finalizeWorkFolder( - UserHandle user, final ArrayList<ShortcutInfo> workFolderApps, - ArrayList<ShortcutInfo> homescreenApps) { - if (workFolderApps.isEmpty()) { - return; + public ItemInfo convertToWorkspaceItem( + ShortcutInfo shortcut, LauncherActivityInfo activityInfo) { + if (activityInfo.getFirstInstallTime() >= addIconToFolderTime) { + return shortcut; } - // 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(); + + if (folderAlreadyCreated) { + if (folderInfo == null) { + // Work folder was deleted by user, add icon to home screen. + return shortcut; + } else { + // Add item to work folder instead. Nothing needs to be added + // on the homescreen. + pendingShortcuts.add(shortcut); + return null; } + } + + pendingShortcuts.add(shortcut); + folderInfo.add(shortcut, false); + if (folderPendingAddition) { + folderPendingAddition = false; + return folderInfo; + } else { + // WorkFolder already requested to be added. Nothing new needs to be added. + return null; + } + } + + public void applyPendingState(ModelWriter writer) { + if (folderInfo == null) { return; } - if (mPrefs.contains(folderIdKey)) { - long folderId = mPrefs.getLong(folderIdKey, 0); - final FolderInfo workFolder = mModel.findFolderById(folderId); - - if (workFolder == null || !workFolder.hasOption(FolderInfo.FLAG_WORK_FOLDER)) { - // Could not get a work folder. Add all the icons to homescreen. - homescreenApps.addAll(0, workFolderApps); - return; - } - saveWorkFolderShortcuts(folderId, workFolder.contents.size(), workFolderApps); + int startingRank = 0; + if (folderAlreadyCreated) { + startingRank = folderInfo.contents.size(); + } + + for (ShortcutInfo info : pendingShortcuts) { + info.rank = startingRank++; + writer.addItemToDatabase(info, folderInfo.id, 0, 0, 0); + } + + if (folderAlreadyCreated) { // FolderInfo could already be bound. We need to add shortcuts on the UI thread. new MainThreadExecutor().execute(new Runnable() { @Override public void run() { - workFolder.prepareAutoUpdate(); - for (ShortcutInfo info : workFolderApps) { - workFolder.add(info, false); + folderInfo.prepareAutoUpdate(); + for (ShortcutInfo info : pendingShortcuts) { + folderInfo.add(info, false); } } }); } else { - // Create a new folder. - final FolderInfo workFolder = new FolderInfo(); - workFolder.title = mContext.getText(R.string.work_folder_name); - workFolder.setOption(FolderInfo.FLAG_WORK_FOLDER, true, null); - - // Add all shortcuts before adding it to the UI, as an empty folder might get deleted. - for (ShortcutInfo info : workFolderApps) { - workFolder.add(info, false); - } - - // Add the item to home screen and DB. This also generates an item id synchronously. - ArrayList<ItemInfo> itemList = new ArrayList<>(1); - itemList.add(workFolder); - mModel.addAndBindAddedWorkspaceItems(itemList); - mPrefs.edit().putLong(folderIdKey, workFolder.id).apply(); - - saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps); + prefs.edit().putLong(folderIdKey, folderInfo.id).apply(); } } - - @Override - public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts, - UserHandle user) { - // Do nothing - } - } - - /** - * Add work folder shortcuts to the DB. - */ - private void saveWorkFolderShortcuts( - long workFolderId, int startingRank, ArrayList<ShortcutInfo> workFolderApps) { - for (ItemInfo info : workFolderApps) { - info.rank = startingRank++; - mModel.getWriter(false).addItemToDatabase(info, workFolderId, 0, 0, 0); - } } /** @@ -225,14 +209,12 @@ public class ManagedProfileHeuristic { */ public static void processAllUsers(List<UserHandle> users, Context context) { UserManagerCompat userManager = UserManagerCompat.getInstance(context); - HashSet<String> validKeys = new HashSet<String>(); + HashSet<String> validKeys = new HashSet<>(); for (UserHandle user : users) { - addAllUserKeys(userManager.getSerialNumberForUser(user), validKeys); + validKeys.add(USER_FOLDER_ID_PREFIX + userManager.getSerialNumberForUser(user)); } - SharedPreferences prefs = context.getSharedPreferences( - LauncherFiles.MANAGED_USER_PREFERENCES_KEY, - Context.MODE_PRIVATE); + SharedPreferences prefs = prefs(context); SharedPreferences.Editor editor = prefs.edit(); for (String key : prefs.getAll().keySet()) { if (!validKeys.contains(key)) { @@ -242,11 +224,6 @@ public class ManagedProfileHeuristic { editor.apply(); } - private static void addAllUserKeys(long userSerial, HashSet<String> keysOut) { - keysOut.add(INSTALLED_PACKAGES_PREFIX + userSerial); - keysOut.add(USER_FOLDER_ID_PREFIX + userSerial); - } - /** * For each user, if a work folder has not been created, mark it such that the folder will * never get created. @@ -260,11 +237,8 @@ public class ManagedProfileHeuristic { if (myUser.equals(user)) { continue; } - if (prefs == null) { - prefs = context.getSharedPreferences( - LauncherFiles.MANAGED_USER_PREFERENCES_KEY, - Context.MODE_PRIVATE); + prefs = prefs(context); } String folderIdKey = USER_FOLDER_ID_PREFIX + userManager.getSerialNumberForUser(user); if (!prefs.contains(folderIdKey)) { @@ -272,4 +246,9 @@ public class ManagedProfileHeuristic { } } } + + public static SharedPreferences prefs(Context context) { + return context.getSharedPreferences( + LauncherFiles.MANAGED_USER_PREFERENCES_KEY, Context.MODE_PRIVATE); + } } diff --git a/src/com/android/launcher3/util/MultiStateAlphaController.java b/src/com/android/launcher3/util/MultiStateAlphaController.java deleted file mode 100644 index 956fc9eba..000000000 --- a/src/com/android/launcher3/util/MultiStateAlphaController.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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.util; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.content.Context; -import android.view.View; -import android.view.accessibility.AccessibilityManager; - -import java.util.Arrays; - -/** - * A utility class which divides the alpha for a view across multiple states. - */ -public class MultiStateAlphaController { - - private final View mTargetView; - private final float[] mAlphas; - private final AccessibilityManager mAm; - private int mZeroAlphaListenerCount = 0; - - public MultiStateAlphaController(View view, int stateCount) { - mTargetView = view; - mAlphas = new float[stateCount]; - Arrays.fill(mAlphas, 1); - - mAm = (AccessibilityManager) view.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - } - - public void setAlphaAtIndex(float alpha, int index) { - mAlphas[index] = alpha; - updateAlpha(); - } - - private void updateAlpha() { - // Only update the alpha if no zero-alpha animation is running. - if (mZeroAlphaListenerCount > 0) { - return; - } - float finalAlpha = 1; - for (float a : mAlphas) { - finalAlpha = finalAlpha * a; - } - mTargetView.setAlpha(finalAlpha); - mTargetView.setVisibility(finalAlpha > 0 ? View.VISIBLE - : (mAm.isEnabled() ? View.GONE : View.INVISIBLE)); - } - - /** - * Returns an animator which changes the alpha at the index {@param index} - * to {@param finalAlpha}. Alphas at other index are not affected. - */ - public Animator animateAlphaAtIndex(float finalAlpha, final int index) { - final ValueAnimator anim; - - if (Float.compare(finalAlpha, mAlphas[index]) == 0) { - // Return a dummy animator to avoid null checks. - anim = ValueAnimator.ofFloat(0, 0); - } else { - ValueAnimator animator = ValueAnimator.ofFloat(mAlphas[index], finalAlpha); - animator.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - float value = (Float) valueAnimator.getAnimatedValue(); - setAlphaAtIndex(value, index); - } - }); - anim = animator; - } - - if (Float.compare(finalAlpha, 0f) == 0) { - // In case when any channel is animating to 0, and the current alpha is also 0, do not - // update alpha of the target view while the animation is running. - // We special case '0' because if any channel is set to 0, values of other - // channels do not matter. - anim.addListener(new ZeroAlphaAnimatorListener()); - } - return anim; - } - - private class ZeroAlphaAnimatorListener extends AnimatorListenerAdapter { - - private boolean mStartedAtZero = false; - - @Override - public void onAnimationStart(Animator animation) { - mStartedAtZero = Float.compare(mTargetView.getAlpha(), 0f) == 0; - if (mStartedAtZero) { - mZeroAlphaListenerCount++; - mTargetView.setAlpha(0); - } - } - - @Override - public void onAnimationEnd(Animator animation) { - if (mStartedAtZero) { - mZeroAlphaListenerCount--; - updateAlpha(); - } - } - } -} diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java index e12b2d4f1..13034dd9e 100644 --- a/src/com/android/launcher3/util/PackageManagerHelper.java +++ b/src/com/android/launcher3/util/PackageManagerHelper.java @@ -30,9 +30,11 @@ import android.os.UserHandle; import android.text.TextUtils; import com.android.launcher3.AppInfo; +import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; +import java.net.URISyntaxException; import java.util.List; /** @@ -149,4 +151,20 @@ public class PackageManagerHelper { .appendQueryParameter("id", packageName) .build()); } + + /** + * Creates a new market search intent. + */ + public static Intent getMarketSearchIntent(Context context, String query) { + try { + Intent intent = Intent.parseUri(context.getString(R.string.market_search_intent), 0); + if (!TextUtils.isEmpty(query)) { + intent.setData( + intent.getData().buildUpon().appendQueryParameter("q", query).build()); + } + return intent; + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } } diff --git a/src/com/android/launcher3/util/PillRevealOutlineProvider.java b/src/com/android/launcher3/util/PillRevealOutlineProvider.java deleted file mode 100644 index a57d69fab..000000000 --- a/src/com/android/launcher3/util/PillRevealOutlineProvider.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.util; - -import android.graphics.Rect; -import android.view.ViewOutlineProvider; - -/** - * A {@link ViewOutlineProvider} that animates a reveal in a "pill" shape. - * A pill is simply a round rect, but we assume the width is greater than - * the height and that the radius is equal to half the height. - */ -public class PillRevealOutlineProvider extends RevealOutlineAnimation { - - private int mCenterX; - private int mCenterY; - private float mFinalRadius; - protected Rect mPillRect; - - /** - * @param x reveal center x - * @param y reveal center y - * @param pillRect round rect that represents the final pill shape - */ - public PillRevealOutlineProvider(int x, int y, Rect pillRect) { - this(x, y, pillRect, pillRect.height() / 2f); - } - - public PillRevealOutlineProvider(int x, int y, Rect pillRect, float radius) { - mCenterX = x; - mCenterY = y; - mPillRect = pillRect; - mOutlineRadius = mFinalRadius = radius; - } - - @Override - public boolean shouldRemoveElevationDuringAnimation() { - return false; - } - - @Override - public void setProgress(float progress) { - // Assumes width is greater than height. - int centerToEdge = Math.max(mCenterX, mPillRect.width() - mCenterX); - int currentSize = (int) (progress * centerToEdge); - - // Bound the outline to the final pill shape defined by mPillRect. - mOutline.left = Math.max(mPillRect.left, mCenterX - currentSize); - mOutline.top = Math.max(mPillRect.top, mCenterY - currentSize); - mOutline.right = Math.min(mPillRect.right, mCenterX + currentSize); - mOutline.bottom = Math.min(mPillRect.bottom, mCenterY + currentSize); - mOutlineRadius = Math.min(mFinalRadius, mOutline.height() / 2); - } -} 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 ef10f97fb..9084bfbd3 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/TestingUtils.java b/src/com/android/launcher3/util/TestingUtils.java index 665c37175..a7cc42b5f 100644 --- a/src/com/android/launcher3/util/TestingUtils.java +++ b/src/com/android/launcher3/util/TestingUtils.java @@ -3,7 +3,6 @@ package com.android.launcher3.util; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.util.Log; import android.view.Gravity; import android.view.View; @@ -11,7 +10,6 @@ import android.widget.FrameLayout; import com.android.launcher3.CustomAppWidget; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.Utilities; diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java index d86333998..89597b9ed 100644 --- a/src/com/android/launcher3/util/Themes.java +++ b/src/com/android/launcher3/util/Themes.java @@ -20,7 +20,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.ColorMatrix; -import android.view.ContextThemeWrapper; +import android.graphics.drawable.Drawable; /** * Various utility methods associated with theming. @@ -31,10 +31,6 @@ public class Themes { return getAttrColor(context, android.R.attr.colorAccent); } - public static int getColorPrimary(Context context, int theme) { - return getAttrColor(new ContextThemeWrapper(context, theme), android.R.attr.colorPrimary); - } - public static int getAttrColor(Context context, int attr) { TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); int colorAccent = ta.getColor(0, 0); @@ -42,6 +38,20 @@ public class Themes { return colorAccent; } + public static boolean getAttrBoolean(Context context, int attr) { + TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); + boolean value = ta.getBoolean(0, false); + ta.recycle(); + return value; + } + + public static Drawable getAttrDrawable(Context context, int attr) { + TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); + Drawable value = ta.getDrawable(0); + ta.recycle(); + return value; + } + /** * Returns the alpha corresponding to the theme attribute {@param attr}, in the range [0, 255]. */ 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(); } diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java index e0a80c679..f47ca3eeb 100644 --- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java +++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java @@ -21,9 +21,8 @@ import android.graphics.Color; import android.support.v7.widget.LinearLayoutManager; import android.util.AttributeSet; import android.view.View; + import com.android.launcher3.BaseRecyclerView; -import com.android.launcher3.model.PackageItemInfo; -import com.android.launcher3.model.WidgetsModel; /** * The widgets recycler view. |