diff options
Diffstat (limited to 'src')
93 files changed, 1584 insertions, 646 deletions
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index 5b42cad96..d7f0180fa 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -18,10 +18,15 @@ 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 com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.ItemInfoMatcher; @@ -69,7 +74,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 +83,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? @@ -169,9 +193,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 +230,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/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/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index bad70183a..add0185ba 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -39,7 +39,7 @@ 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.PreloadIconDrawable; @@ -180,6 +180,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 */); } @@ -475,27 +479,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) { @@ -547,7 +560,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) { @@ -587,6 +602,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { .isEmpty(); } + 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..c0946a0e3 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, android.R.attr.textColorPrimary)); // 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 @@ -568,7 +569,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { try { dispatchRestoreInstanceState(states); } catch (IllegalArgumentException ex) { - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { throw ex; } // Mismatched viewId / viewType preventing restore. Skip restore on production builds. diff --git a/src/com/android/launcher3/DeferredHandler.java b/src/com/android/launcher3/DeferredHandler.java deleted file mode 100644 index a43ab6723..000000000 --- a/src/com/android/launcher3/DeferredHandler.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3; - -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.MessageQueue; - -import com.android.launcher3.util.Thunk; - -import java.util.LinkedList; - -/** - * Queue of things to run on a looper thread. Items posted with {@link #post} will not - * be actually enqued on the handler until after the last one has run, to keep from - * starving the thread. - * - * This class is fifo. - */ -public class DeferredHandler { - @Thunk LinkedList<Runnable> mQueue = new LinkedList<>(); - private MessageQueue mMessageQueue = Looper.myQueue(); - private Impl mHandler = new Impl(); - - @Thunk class Impl extends Handler implements MessageQueue.IdleHandler { - public void handleMessage(Message msg) { - Runnable r; - synchronized (mQueue) { - if (mQueue.size() == 0) { - return; - } - r = mQueue.removeFirst(); - } - r.run(); - synchronized (mQueue) { - scheduleNextLocked(); - } - } - - public boolean queueIdle() { - handleMessage(null); - return false; - } - } - - private class IdleRunnable implements Runnable { - Runnable mRunnable; - - IdleRunnable(Runnable r) { - mRunnable = r; - } - - public void run() { - mRunnable.run(); - } - } - - public DeferredHandler() { - } - - /** Schedule runnable to run after everything that's on the queue right now. */ - public void post(Runnable runnable) { - synchronized (mQueue) { - mQueue.add(runnable); - if (mQueue.size() == 1) { - scheduleNextLocked(); - } - } - } - - /** Schedule runnable to run when the queue goes idle. */ - public void postIdle(final Runnable runnable) { - post(new IdleRunnable(runnable)); - } - - public void cancelAll() { - synchronized (mQueue) { - mQueue.clear(); - } - } - - /** Runs all queued Runnables from the calling thread. */ - public void flush() { - LinkedList<Runnable> queue = new LinkedList<>(); - synchronized (mQueue) { - queue.addAll(mQueue); - mQueue.clear(); - } - for (Runnable r : queue) { - r.run(); - } - } - - void scheduleNextLocked() { - if (mQueue.size() > 0) { - Runnable peek = mQueue.getFirst(); - if (peek instanceof IdleRunnable) { - mMessageQueue.addIdleHandler(mHandler); - } else { - mHandler.sendEmptyMessage(1); - } - } - } -} - diff --git a/src/com/android/launcher3/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/IconCache.java b/src/com/android/launcher3/IconCache.java index 924b79be0..736dfeba1 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -437,9 +437,10 @@ public class IconCache { * Updates {@param application} only if a valid entry is found. */ public synchronized void updateTitleAndIcon(AppInfo application) { + boolean usePackageIcon = application instanceof PromiseAppInfo; CacheEntry entry = cacheLocked(application.componentName, Provider.<LauncherActivityInfo>of(null), - application.user, false, application.usingLowResIcon); + application.user, usePackageIcon, application.usingLowResIcon); if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) { applyCacheEntry(entry, application); } diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java index 34adf47a1..2f61a01ed 100644 --- a/src/com/android/launcher3/InfoDropTarget.java +++ b/src/com/android/launcher3/InfoDropTarget.java @@ -60,6 +60,12 @@ public class InfoDropTarget extends UninstallDropTarget { */ public static boolean startDetailsActivityForInfo( ItemInfo info, Launcher launcher, DropTargetResultCallback callback) { + 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/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 dbf535a80..a8d3d15d5 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -65,6 +65,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; @@ -90,7 +91,6 @@ import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PinItemRequestCompat; 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; @@ -1429,8 +1429,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); @@ -2464,7 +2464,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"); } @@ -3391,7 +3397,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); @@ -3769,6 +3775,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() { diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java index aa7f5ee5f..2be2021f1 100644 --- a/src/com/android/launcher3/LauncherAnimUtils.java +++ b/src/com/android/launcher3/LauncherAnimUtils.java @@ -23,9 +23,7 @@ import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; 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; diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 180c202fa..27ccabea9 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; diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java index 2bac11f97..ff037b8f9 100644 --- a/src/com/android/launcher3/LauncherCallbacks.java +++ b/src/com/android/launcher3/LauncherCallbacks.java @@ -17,13 +17,11 @@ 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,61 +42,61 @@ 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(); + AllAppsSearchBarController getAllAppsSearchBarController(); + 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. @@ -106,7 +104,7 @@ public interface LauncherCallbacks { * @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); + 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 f8c591c13..4cc75acd0 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.LauncherActivityInfo; +import android.content.pm.PackageInstaller; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; @@ -45,10 +46,11 @@ import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dynamicui.ExtractionUtils; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.folder.FolderIconPreviewVerifier; import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.AddWorkspaceItemsTask; @@ -72,6 +74,7 @@ import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.LooperIdleLock; import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageManagerHelper; @@ -111,9 +114,9 @@ public class LauncherModel extends BroadcastReceiver private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons private static final long INVALID_SCREEN_ID = -1L; + private final MainThreadExecutor mUiExecutor = new MainThreadExecutor(); @Thunk final LauncherAppState mApp; @Thunk final Object mLock = new Object(); - @Thunk DeferredHandler mHandler = new DeferredHandler(); @Thunk LoaderTask mLoaderTask; @Thunk boolean mIsLoaderTaskRunning; @Thunk boolean mHasLoaderCompletedOnce; @@ -193,6 +196,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); @@ -219,17 +223,6 @@ public class LauncherModel extends BroadcastReceiver mUserManager = UserManagerCompat.getInstance(context); } - /** Runs the specified runnable immediately if called from the main thread, otherwise it is - * posted on the main thread handler. */ - private void runOnMainThread(Runnable r) { - if (sWorkerThread.getThreadId() == Process.myTid()) { - // If we are on the worker thread, post onto the main handler - mHandler.post(r); - } else { - r.run(); - } - } - /** Runs the specified runnable immediately if called from the worker thread, otherwise it is * posted on the worker thread handler. */ private static void runOnWorkerThread(Runnable r) { @@ -379,8 +372,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); } } @@ -544,11 +535,11 @@ public class LauncherModel extends BroadcastReceiver if (mCallbacks != null && mCallbacks.get() != null) { final Callbacks oldCallbacks = mCallbacks.get(); // Clear any pending bind-runnables from the synchronized load process. - runOnMainThread(new Runnable() { - public void run() { - oldCallbacks.clearPendingBinds(); - } - }); + mUiExecutor.execute(new Runnable() { + public void run() { + oldCallbacks.clearPendingBinds(); + } + }); // If there is already one running, tell it to stop. stopLoaderLocked(); @@ -586,6 +577,25 @@ public class LauncherModel extends BroadcastReceiver screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK)); } + 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); + } + }); + } + } + }); + } + /** * Runnable for the thread that loads the contents of the launcher: * - workspace icons @@ -599,7 +609,6 @@ public class LauncherModel extends BroadcastReceiver @Thunk boolean mIsLoadingAndBindingWorkspace; private boolean mStopped; - @Thunk boolean mLoadAndBindStepFinished; LoaderTask(Context context, int pageToBindFirst) { mContext = context; @@ -611,34 +620,10 @@ public class LauncherModel extends BroadcastReceiver // This way we don't start loading all apps until the workspace has settled // down. synchronized (LoaderTask.this) { - final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; - - mHandler.postIdle(new Runnable() { - public void run() { - synchronized (LoaderTask.this) { - mLoadAndBindStepFinished = true; - if (DEBUG_LOADERS) { - Log.d(TAG, "done with previous binding step"); - } - LoaderTask.this.notify(); - } - } - }); - - while (!mStopped && !mLoadAndBindStepFinished) { - try { - // Just in case mFlushingWorkerThread changes but we aren't woken up, - // wait no longer than 1sec at a time - this.wait(1000); - } catch (InterruptedException ex) { - // Ignore - } - } - if (DEBUG_LOADERS) { - Log.d(TAG, "waited " - + (SystemClock.uptimeMillis()-workspaceWaitTime) - + "ms for previous step to finish binding"); - } + LooperIdleLock idleLock = new LooperIdleLock(this, Looper.getMainLooper()); + // Just in case mFlushingWorkerThread changes but we aren't woken up, + // wait no longer than 1sec at a time + while (!mStopped && idleLock.awaitLocked(1000)); } } @@ -661,15 +646,6 @@ public class LauncherModel extends BroadcastReceiver } } - // XXX: Throw an exception if we are already loading (since we touch the worker thread - // data structures, we can't allow any other thread to touch that data, but because - // this call is synchronous, we can get away with not locking). - - // The LauncherModel is static in the LauncherAppState and mHandler may have queued - // operations from the previous activity. We need to ensure that all queued operations - // are executed before any synchronous binding work is done. - mHandler.flush(); - // Divide the set of loaded items into those that we are binding synchronously, and // everything else that is to be bound normally (asynchronously). bindWorkspace(synchronousBindPage); @@ -697,6 +673,7 @@ public class LauncherModel extends BroadcastReceiver } try { + long now = 0; if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace"); // Set to false in bindWorkspace() mIsLoadingAndBindingWorkspace = true; @@ -707,8 +684,12 @@ public class LauncherModel extends BroadcastReceiver bindWorkspace(mPageToBindFirst); // Take a break - if (DEBUG_LOADERS) Log.d(TAG, "step 1 completed, wait for idle"); + 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 @@ -720,8 +701,12 @@ public class LauncherModel extends BroadcastReceiver updateIconCache(); // Take a break - if (DEBUG_LOADERS) Log.d(TAG, "step 2 completed, wait for idle"); + 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 @@ -890,6 +875,8 @@ public class LauncherModel extends BroadcastReceiver Intent intent; String targetPkg; + FolderIconPreviewVerifier verifier = + new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile()); while (!mStopped && c.moveToNext()) { try { if (c.user == null) { @@ -944,7 +931,7 @@ public class LauncherModel extends BroadcastReceiver // no special handling necessary for this item c.markRestored(); } else { - if (c.hasRestoreFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) { + 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); @@ -1014,7 +1001,7 @@ public class LauncherModel extends BroadcastReceiver } boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() && - c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW; + !verifier.isItemInPreview(c.getInt(rankIndex)); if (c.restoreFlag != 0) { // Already verified above that user is same as default user @@ -1263,17 +1250,23 @@ public class LauncherModel extends BroadcastReceiver } } - // Sort all the folder items and make sure the first 3 items are high resolution. + FolderIconPreviewVerifier verifier = + new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile()); + // Sort the folder items and make sure all items in the preview are high resolution. for (FolderInfo folder : sBgDataModel.folders) { Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR); - int pos = 0; + verifier.setFolderInfo(folder); + + int numItemsInPreview = 0; for (ShortcutInfo info : folder.contents) { - if (info.usingLowResIcon && - info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { + if (info.usingLowResIcon + && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION + && verifier.isItemInPreview(info.rank)) { mIconCache.getTitleAndIcon(info, false); + numItemsInPreview++; } - pos ++; - if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) { + + if (numItemsInPreview >= FolderIcon.NUM_ITEMS_IN_PREVIEW) { break; } } @@ -1398,7 +1391,7 @@ public class LauncherModel extends BroadcastReceiver return Utilities.longCompare(lhs.screenId, rhs.screenId); } default: - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { throw new RuntimeException("Unexpected container type when " + "sorting workspace items."); } @@ -1423,7 +1416,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r); + mUiExecutor.execute(r); } private void bindWorkspaceItems(final Callbacks oldCallbacks, @@ -1529,11 +1522,11 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r); + mUiExecutor.execute(r); bindWorkspaceScreens(oldCallbacks, orderedScreenIds); - Executor mainExecutor = new DeferredMainThreadExecutor(); + Executor mainExecutor = mUiExecutor; // Load items on the current page. bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor); @@ -1543,7 +1536,7 @@ public class LauncherModel extends BroadcastReceiver // This ensures that the first screen is immediately visible (eg. during rotation) // In case of !validFirstPage, bind all pages one after other. final Executor deferredExecutor = - validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor; + validFirstPage ? new ViewOnDrawExecutor(mUiExecutor) : mainExecutor; mainExecutor.execute(new Runnable() { @Override @@ -1603,7 +1596,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r); + mUiExecutor.execute(r); } } @@ -1653,7 +1646,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r); + mUiExecutor.execute(r); } private void loadAllApps() { @@ -1701,29 +1694,40 @@ public class LauncherModel extends BroadcastReceiver heuristic.processUserApps(apps); } }; - runOnMainThread(new Runnable() { + mUiExecutor.execute(new Runnable() { + + @Override + public void run() { + // Check isLoadingWorkspace on the UI thread, as it is updated on + // the UI thread. + if (mIsLoadingAndBindingWorkspace) { + synchronized (mBindCompleteRunnables) { + mBindCompleteRunnables.add(r); + } + } else { + runOnWorkerThread(r); + } + } + }); + } + } - @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); - } - } - }); + if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) { + // get all active sessions and add them to the all apps list + PackageInstallerCompat installer = PackageInstallerCompat.getInstance(mContext); + for (PackageInstaller.SessionInfo info : installer.getAllVerifiedSessions()) { + mBgAllAppsList.addPromiseApp(mContext, + PackageInstallInfo.fromInstallingState(info)); } } + // 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() { + mUiExecutor.execute(new Runnable() { public void run() { final long bindTime = SystemClock.uptimeMillis(); @@ -1777,7 +1781,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r); + mUiExecutor.execute(r); } /** @@ -1828,12 +1832,12 @@ public class LauncherModel extends BroadcastReceiver public static abstract class BaseModelUpdateTask implements Runnable { private LauncherModel mModel; - private DeferredHandler mUiHandler; + private Executor mUiExecutor; /* package private */ void init(LauncherModel model) { mModel = model; - mUiHandler = mModel.mHandler; + mUiExecutor = mModel.mUiExecutor; } @Override @@ -1856,7 +1860,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) { @@ -1901,7 +1905,7 @@ public class LauncherModel extends BroadcastReceiver private void bindWidgetsModel(final Callbacks callbacks) { final MultiHashMap<PackageItemInfo, WidgetItem> widgets = mBgWidgetsModel.getWidgetsMap().clone(); - mHandler.post(new Runnable() { + mUiExecutor.execute(new Runnable() { @Override public void run() { Callbacks cb = getCallback(); @@ -1959,14 +1963,6 @@ public class LauncherModel extends BroadcastReceiver } } - @Thunk class DeferredMainThreadExecutor implements Executor { - - @Override - public void execute(Runnable command) { - runOnMainThread(command); - } - } - /** * @return the looper for the worker thread which can be used to start background tasks. */ diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 65d76728d..a3d9dbc9c 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -47,14 +47,12 @@ import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; import android.util.Log; -import android.view.ViewGroup; import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback; 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; @@ -78,7 +76,7 @@ public class LauncherProvider extends ContentProvider { private static final int DATABASE_VERSION = 28; - 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"; @@ -103,7 +101,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); diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index af2c10275..3f047f511 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..31e3dda32 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) { 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 203bc2523..69695ae81 100644 --- a/src/com/android/launcher3/SessionCommitReceiver.java +++ b/src/com/android/launcher3/SessionCommitReceiver.java @@ -29,7 +29,6 @@ import android.text.TextUtils; import android.util.Log; import com.android.launcher3.compat.LauncherAppsCompat; -import com.android.launcher3.compat.UserManagerCompat; import java.util.List; 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/Utilities.java b/src/com/android/launcher3/Utilities.java index 207a7d4af..776ec2f62 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -51,7 +51,7 @@ import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; import java.io.ByteArrayOutputStream; import java.io.Closeable; @@ -61,7 +61,6 @@ import java.lang.reflect.Method; import java.util.Collection; import java.util.HashSet; import java.util.Locale; -import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; @@ -570,7 +569,7 @@ public final class Utilities { try { c.close(); } catch (IOException e) { - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { Log.d(TAG, "Error closing", e); } } diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index fc3b66887..4ae3649df 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -435,19 +435,20 @@ public class WidgetPreviewLoader { float shadowBlur = res.getDimension(R.dimen.widget_preview_shadow_blur); float keyShadowDistance = res.getDimension(R.dimen.widget_preview_key_shadow_distance); float corner = res.getDimension(R.dimen.widget_preview_corner_radius); + int shadowColor = ColorUtils.setAlphaComponent( + res.getColor(R.color.default_shadow_color_no_alpha), + ShadowGenerator.AMBIENT_SHADOW_ALPHA); 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); + p.setShadowLayer(shadowBlur, 0, keyShadowDistance, shadowColor); c.drawRoundRect(bounds, corner, corner, p); // Ambient shadow - p.setShadowLayer(shadowBlur, 0, 0, - ColorUtils.setAlphaComponent(Color.BLACK, ShadowGenerator.AMBIENT_SHADOW_ALPHA)); + p.setShadowLayer(shadowBlur, 0, 0, shadowColor); c.drawRoundRect(bounds, corner, corner, p); p.clearShadowLayer(); diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 7562dd8b3..8f8d32ccc 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; @@ -528,8 +528,6 @@ public class Workspace extends PagedView // Set the wallpaper dimensions when Launcher starts up setWallpaperDimension(); - - setEdgeGlowColor(getResources().getColor(R.color.workspace_edge_effect_color)); } @Override @@ -622,7 +620,7 @@ 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( + qsb = LayoutInflater.from(getContext()).inflate( mLauncher.getDeviceProfile().isVerticalBarLayout() ? R.layout.qsb_container : R.layout.qsb_blocker_view, firstPage, false); @@ -708,7 +706,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); @@ -726,7 +724,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(); @@ -2619,7 +2617,7 @@ public class Workspace extends PagedView CellLayout parentCell = getParentCellLayoutForView(cell); if (parentCell != null) { parentCell.removeView(cell); - } else if (ProviderConfig.IS_DOGFOOD_BUILD) { + } else if (FeatureFlags.IS_DOGFOOD_BUILD) { throw new NullPointerException("mDragInfo.cell has null parent"); } addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1], @@ -2952,7 +2950,7 @@ public class Workspace extends PagedView ItemInfo item = d.dragInfo; if (item == null) { - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { throw new NullPointerException("DragObject has null info"); } return; @@ -3609,7 +3607,7 @@ public class Workspace extends PagedView mDragInfo.container, mDragInfo.screenId); if (cellLayout != null) { cellLayout.onDropChild(mDragInfo.cell); - } else if (ProviderConfig.IS_DOGFOOD_BUILD) { + } else if (FeatureFlags.IS_DOGFOOD_BUILD) { throw new RuntimeException("Invalid state: cellLayout == null in " + "Workspace#onDropCompleted. Please file a bug. "); }; @@ -3635,7 +3633,7 @@ public class Workspace extends PagedView CellLayout parentCell = getParentCellLayoutForView(v); if (parentCell != null) { parentCell.removeView(v); - } else if (ProviderConfig.IS_DOGFOOD_BUILD) { + } else if (FeatureFlags.IS_DOGFOOD_BUILD) { // When an app is uninstalled using the drop target, we wait until resume to remove // the icon. We also remove all the corresponding items from the workspace at // {@link Launcher#bindComponentsRemoved}. That call can come before or after 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 a476650c9..e8127c45f 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.util.Thunk; import java.util.ArrayList; diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java index b784fe7f8..2ad0edbbc 100644 --- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java @@ -22,7 +22,6 @@ import android.view.accessibility.AccessibilityNodeInfo; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.ShortcutInfo; 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/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index cc5fa8ce1..7219141cd 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -37,6 +37,7 @@ import android.view.ViewGroup; import com.android.launcher3.AppInfo; import com.android.launcher3.BaseContainerView; +import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; @@ -45,6 +46,7 @@ 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.config.FeatureFlags; @@ -158,6 +160,17 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mSearchBarController.refreshSearchResult(); } + 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); + } + } + } + /** * Removes some apps from the list. */ 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..938e84ed0 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -33,12 +33,12 @@ 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.allapps.AlphabeticalAppsList.AdapterItem; +import com.android.launcher3.discovery.AppDiscoveryAppInfo; import com.android.launcher3.discovery.AppDiscoveryItemView; import java.util.List; 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/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index f5cf7effb..f291a80ee 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; @@ -440,7 +440,7 @@ public class AlphabeticalAppsList { if (info != null) { mPredictedApps.add(info); } else { - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { Log.e(TAG, "Predicted app not found: " + ck); } } 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 index be1e2d644..679e8e32f 100644 --- a/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java +++ b/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java @@ -18,8 +18,6 @@ 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. diff --git a/src/com/android/launcher3/util/PillRevealOutlineProvider.java b/src/com/android/launcher3/anim/PillRevealOutlineProvider.java index a57d69fab..450f9db9a 100644 --- a/src/com/android/launcher3/util/PillRevealOutlineProvider.java +++ b/src/com/android/launcher3/anim/PillRevealOutlineProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3.util; +package com.android.launcher3.anim; import android.graphics.Rect; import android.view.ViewOutlineProvider; diff --git a/src/com/android/launcher3/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..a0d1f8b65 --- /dev/null +++ b/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.anim; + +import android.graphics.Rect; + +/** + * A {@link RevealOutlineAnimation} that provides an outline that interpolates between two radii + * and two {@link Rect}s. + * + * An example usage of this provider is an outline that starts out as a circle and ends + * as a rounded rectangle. + */ +public class RoundedRectRevealOutlineProvider extends RevealOutlineAnimation { + private final float mStartRadius; + private final float mEndRadius; + + private final Rect mStartRect; + private final Rect mEndRect; + + public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect, + Rect endRect) { + mStartRadius = startRadius; + mEndRadius = endRadius; + mStartRect = startRect; + mEndRect = endRect; + } + + @Override + public boolean shouldRemoveElevationDuringAnimation() { + return true; + } + + @Override + public void setProgress(float progress) { + mOutlineRadius = (1 - progress) * mStartRadius + progress * mEndRadius; + + mOutline.left = (int) ((1 - progress) * mStartRect.left + progress * mEndRect.left); + mOutline.top = (int) ((1 - progress) * mStartRect.top + progress * mEndRect.top); + mOutline.right = (int) ((1 - progress) * mStartRect.right + progress * mEndRect.right); + mOutline.bottom = (int) ((1 - progress) * mStartRect.bottom + progress * mEndRect.bottom); + } +} diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java index f17d8deef..01d0e1784 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompat.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java @@ -32,7 +32,7 @@ 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.LooperExecutor; import com.android.launcher3.util.PackageUserKey; import java.util.List; @@ -115,7 +115,7 @@ public abstract class LauncherAppsCompat { } } else { // Block the worker thread until the accept() is called. - new LooperExecuter(LauncherModel.getWorkerLooper()).execute(new Runnable() { + new LooperExecutor(LauncherModel.getWorkerLooper()).execute(new Runnable() { @Override public void run() { try { 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..bbf154629 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java @@ -17,6 +17,7 @@ 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; @@ -28,23 +29,31 @@ 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 +61,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 +95,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 +113,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 +135,46 @@ 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) { + 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/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/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index b04d5b747..705262e5b 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -58,7 +58,6 @@ 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.userevent.nano.LauncherLogProto; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.TouchController; import com.android.launcher3.widget.WidgetsAndMore; 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/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..a0ceb49d7 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -46,6 +46,7 @@ import android.widget.TextView; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Alarm; import com.android.launcher3.AppInfo; +import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; @@ -66,8 +67,9 @@ 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,12 @@ 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 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 +135,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 +505,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 +571,75 @@ 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.setVisibility(INVISIBLE); + } + Utilities.sendCustomAccessibilityEvent( Folder.this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, mContent.getAccessibilityDescription()); - mState = STATE_ANIMATING; } @Override public void onAnimationEnd(Animator animation) { @@ -650,7 +685,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()) { @@ -688,7 +723,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC mFolderName.dispatchBackKey(); } - if (mFolderIcon != null) { + if (mFolderIcon != null && !FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) { mFolderIcon.shrinkAndFadeIn(animate); } @@ -706,12 +741,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 +767,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,10 +780,14 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } mDragController.removeDropTarget(this); clearFocus(); - if (wasAnimated) { - mFolderIcon.requestFocus(); + if (mFolderIcon != null) { + mFolderIcon.setVisibility(View.VISIBLE); + if (wasAnimated) { + mFolderIcon.requestFocus(); + } } + if (mRearrangeOnClose) { rearrangeChildren(); mRearrangeOnClose = false; @@ -1027,6 +1075,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; } @@ -1427,6 +1478,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..578921f64 --- /dev/null +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -0,0 +1,361 @@ +/* + * 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); + + // 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); + + 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. + float initialSize = (mFolderIcon.mBackground.getRadius() * 2 + + mPreviewBackground.getStrokeWidth()) * scaleRelativeToDragLayer; + + 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 f21601092..ced9c9e8d 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -125,6 +125,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; @@ -179,7 +180,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); @@ -219,6 +221,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private void setFolder(Folder folder) { mFolder = folder; + mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv); updateItemDrawingParams(false); } @@ -405,6 +408,10 @@ public class FolderIcon extends FrameLayout implements FolderListener { mBadgeInfo = badgeInfo; } + public PreviewLayoutRule getLayoutRule() { + return mPreviewLayoutRule; + } + /** * Sets mBadgeScale to 1 or 0, animating if oldCount or newCount is 0 * (the badge is being added or removed). @@ -822,6 +829,14 @@ public class FolderIcon extends FrameLayout implements FolderListener { }; animateScale(1f, 1f, onStart, onEnd); } + + public int getBackgroundAlpha() { + return (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier); + } + + public float getStrokeWidth() { + return mStrokeWidth; + } } public void setFolderBackground(PreviewBackground bg) { @@ -989,8 +1004,26 @@ public class FolderIcon extends FrameLayout implements FolderListener { return mFolderName.getVisibility() == VISIBLE; } + public List<BubbleTextView> getItemsToDisplay() { + mPreviewVerifier.setFolderInfo(mFolder.getInfo()); + + List<BubbleTextView> itemsToDisplay = new ArrayList<>(); + List<View> allItems = mFolder.getItemsInReadingOrder(); + int numItems = allItems.size(); + for (int rank = 0; rank < numItems; ++rank) { + if (mPreviewVerifier.isItemInPreview(rank)) { + itemsToDisplay.add((BubbleTextView) allItems.get(rank)); + } + + if (itemsToDisplay.size() == FolderIcon.NUM_ITEMS_IN_PREVIEW) { + break; + } + } + return itemsToDisplay; + } + private void updateItemDrawingParams(boolean animate) { - List<View> items = mPreviewLayoutRule.getItemsToDisplay(mFolder); + List<BubbleTextView> items = getItemsToDisplay(); int nItemsInPreview = items.size(); int prevNumItems = mDrawingParams.size(); @@ -1005,7 +1038,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { for (int i = 0; i < mDrawingParams.size(); i++) { PreviewItemDrawingParams p = mDrawingParams.get(i); - p.drawable = ((TextView) items.get(i)).getCompoundDrawables()[1]; + p.drawable = items.get(i).getCompoundDrawables()[1]; if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) { computePreviewItemDrawingParams(i, nItemsInPreview, p); @@ -1174,8 +1207,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { PreviewItemDrawingParams params); void init(int availableSpace, int intrinsicIconSize, boolean rtl); float scaleForItem(int index, int totalNumItems); + float getIconSize(); int maxNumItems(); boolean clipToBackground(); - List<View> getItemsToDisplay(Folder folder); } } diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java new file mode 100644 index 000000000..de962b021 --- /dev/null +++ b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.folder; + +import com.android.launcher3.FolderInfo; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.config.FeatureFlags; + +/** + * Verifies whether an item in a Folder is displayed in the FolderIcon preview. + */ +public class FolderIconPreviewVerifier { + + private final int mMaxGridCountX; + private final int mMaxGridCountY; + private final int mMaxItemsPerPage; + private final int[] mGridSize = new int[2]; + + private int mGridCountX; + private boolean mDisplayingUpperLeftQuadrant = false; + + public FolderIconPreviewVerifier(InvariantDeviceProfile profile) { + mMaxGridCountX = profile.numFolderColumns; + mMaxGridCountY = profile.numFolderRows; + mMaxItemsPerPage = mMaxGridCountX * mMaxGridCountY; + } + + public void setFolderInfo(FolderInfo info) { + int numItemsInFolder = info.contents.size(); + mDisplayingUpperLeftQuadrant = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION + && !FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON + && numItemsInFolder > FolderIcon.NUM_ITEMS_IN_PREVIEW; + + if (mDisplayingUpperLeftQuadrant) { + FolderPagedView.calculateGridSize(info.contents.size(), 0, 0, mMaxGridCountX, + mMaxGridCountY, mMaxItemsPerPage, mGridSize); + mGridCountX = mGridSize[0]; + } + } + + public boolean isItemInPreview(int rank) { + if (mDisplayingUpperLeftQuadrant) { + // Returns true iff the icon is in the 2x2 upper left quadrant of the Folder. + int col = rank % mGridCountX; + int row = rank / mGridCountX; + return col < 2 && row < 2; + } + return rank < FolderIcon.NUM_ITEMS_IN_PREVIEW; + } +} diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index eecce183a..2a6007a4e 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); @@ -314,6 +328,8 @@ public class FolderPagedView extends PagedView { int position = 0; int newX, newY, rank; + FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier( + Launcher.getLauncher(getContext()).getDeviceProfile().inv); rank = 0; for (int i = 0; i < itemCount; i++) { View v = list.size() > i ? list.get(i) : null; @@ -346,7 +362,7 @@ public class FolderPagedView extends PagedView { currentPage.addViewToCellLayout( v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true); - if (rank < FolderIcon.NUM_ITEMS_IN_PREVIEW && v instanceof BubbleTextView) { + if (verifier.isItemInPreview(rank) && v instanceof BubbleTextView) { ((BubbleTextView) v).verifyHighRes(); } } @@ -400,12 +416,12 @@ public class FolderPagedView extends PagedView { public int findNearestArea(int pixelX, int pixelY) { int pageIndex = getNextPage(); CellLayout page = getPageAt(pageIndex); - page.findNearestArea(pixelX, pixelY, 1, 1, sTempPosArray); + page.findNearestArea(pixelX, pixelY, 1, 1, sTmpArray); if (mFolder.isLayoutRtl()) { - sTempPosArray[0] = page.getCountX() - sTempPosArray[0] - 1; + sTmpArray[0] = page.getCountX() - sTmpArray[0] - 1; } return Math.min(mAllocatedContentSize - 1, - pageIndex * mMaxItemsPerPage + sTempPosArray[1] * mGridCountX + sTempPosArray[0]); + pageIndex * mMaxItemsPerPage + sTmpArray[1] * mGridCountX + sTmpArray[0]); } public boolean isFull() { diff --git a/src/com/android/launcher3/folder/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/FixedScaleDrawable.java b/src/com/android/launcher3/graphics/FixedScaleDrawable.java index 4be4bd552..7ee3d8002 100644 --- a/src/com/android/launcher3/graphics/FixedScaleDrawable.java +++ b/src/com/android/launcher3/graphics/FixedScaleDrawable.java @@ -19,15 +19,17 @@ public class FixedScaleDrawable extends DrawableWrapper { // TODO b/33553066 use the constant defined in MaskableIconDrawable private static final float LEGACY_ICON_SCALE = .7f * .6667f; + private float mScale; public FixedScaleDrawable() { super(new ColorDrawable()); + mScale = LEGACY_ICON_SCALE; } @Override public void draw(Canvas canvas) { int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.scale(LEGACY_ICON_SCALE, LEGACY_ICON_SCALE, + canvas.scale(mScale, mScale, getBounds().exactCenterX(), getBounds().exactCenterY()); super.draw(canvas); canvas.restoreToCount(saveCount); @@ -38,4 +40,8 @@ public class FixedScaleDrawable extends DrawableWrapper { @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { } + + public void setScale(float scale) { + mScale = scale * LEGACY_ICON_SCALE; + } } 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/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java index 70b3dd6c9..13f322e8b 100644 --- a/src/com/android/launcher3/graphics/IconNormalizer.java +++ b/src/com/android/launcher3/graphics/IconNormalizer.java @@ -20,15 +20,31 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.Utilities; +import java.io.File; +import java.io.FileOutputStream; import java.nio.ByteBuffer; +import java.util.Random; public class IconNormalizer { + private static final String TAG = "IconNormalizer"; + private static final boolean DEBUG = false; // Ratio of icon visible area to full icon size for a square shaped icon private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576; // Ratio of icon visible area to full icon size for a circular shaped icon @@ -42,17 +58,36 @@ public class IconNormalizer { private static final int MIN_VISIBLE_ALPHA = 40; + // Shape detection related constants + private static final float BOUND_RATIO_MARGIN = .05f; + private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f; + private static final float SCALE_NOT_INITIALIZED = 0; + private static final Object LOCK = new Object(); private static IconNormalizer sIconNormalizer; private final int mMaxSize; private final Bitmap mBitmap; + private final Bitmap mBitmapARGB; private final Canvas mCanvas; + private final Paint mPaintMaskShape; + private final Paint mPaintMaskShapeOutline; private final byte[] mPixels; + private final int[] mPixelsARGB; + private float mAdaptiveIconScale; // for each y, stores the position of the leftmost x and the rightmost x private final float[] mLeftBorder; private final float[] mRightBorder; + private final Rect mBounds; + private final Matrix mMatrix; + + private Paint mPaintIcon; + private Canvas mCanvasARGB; + + private File mDir; + private int mFileId; + private Random mRandom; private IconNormalizer(Context context) { // Use twice the icon size as maximum size to avoid scaling down twice. @@ -60,9 +95,121 @@ public class IconNormalizer { mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8); mCanvas = new Canvas(mBitmap); mPixels = new byte[mMaxSize * mMaxSize]; - + mPixelsARGB = new int[mMaxSize * mMaxSize]; mLeftBorder = new float[mMaxSize]; mRightBorder = new float[mMaxSize]; + mBounds = new Rect(); + + // Needed for isShape() method + mBitmapARGB = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ARGB_8888); + mCanvasARGB = new Canvas(mBitmapARGB); + + mPaintIcon = new Paint(); + mPaintIcon.setColor(Color.WHITE); + + mPaintMaskShape = new Paint(); + mPaintMaskShape.setColor(Color.RED); + mPaintMaskShape.setStyle(Paint.Style.FILL); + mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR)); + + mPaintMaskShapeOutline = new Paint(); + mPaintMaskShapeOutline.setStrokeWidth(2 * context.getResources().getDisplayMetrics().density); + mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE); + mPaintMaskShapeOutline.setColor(Color.BLACK); + mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + + mMatrix = new Matrix(); + int[] mPixels = new int[mMaxSize * mMaxSize]; + mAdaptiveIconScale = SCALE_NOT_INITIALIZED; + + mDir = context.getExternalFilesDir(null); + mRandom = new Random(); + } + + /** + * Returns if the shape of the icon is same as the path. + * For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds. + */ + private boolean isShape(Path maskPath) { + // Condition1: + // If width and height of the path not close to a square, then the icon shape is + // not same as the mask shape. + float iconRatio = ((float) mBounds.width()) / mBounds.height(); + if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) { + if (DEBUG) { + Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio); + } + return false; + } + + // Condition 2: + // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation + // should generate transparent image, if the actual icon is equivalent to the shape. + mFileId = mRandom.nextInt(); + mBitmapARGB.eraseColor(Color.TRANSPARENT); + mCanvasARGB.drawBitmap(mBitmap, 0, 0, mPaintIcon); + + if (DEBUG) { + final File beforeFile = new File(mDir, "isShape" + mFileId + "_before.png"); + try { + mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100, + new FileOutputStream(beforeFile)); + } catch (Exception e) {} + } + + // Fit the shape within the icon's bounding box + mMatrix.reset(); + mMatrix.setScale(mBounds.width(), mBounds.height()); + mMatrix.postTranslate(mBounds.left, mBounds.top); + maskPath.transform(mMatrix); + + // XOR operation + mCanvasARGB.drawPath(maskPath, mPaintMaskShape); + + // DST_OUT operation around the mask path outline + mCanvasARGB.drawPath(maskPath, mPaintMaskShapeOutline); + + boolean isTrans = isTransparentBitmap(mBitmapARGB); + if (DEBUG) { + final File afterFile = new File(mDir, "isShape" + mFileId + "_after_" + isTrans + ".png"); + try { + mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100, + new FileOutputStream(afterFile)); + } catch (Exception e) {} + } + + // Check if the result is almost transparent + if (!isTrans) { + if (DEBUG) { + Log.d(TAG, "Not same as mask shape"); + } + return false; + } + return true; + } + + /** + * Used to determine if certain the bitmap is transparent. + */ + private boolean isTransparentBitmap(Bitmap bitmap) { + int w = mBounds.width(); + int h = mBounds.height(); + bitmap.getPixels(mPixelsARGB, 0 /* the first index to write into the array */, + w /* stride */, + mBounds.left, mBounds.top, + w, h); + int sum = 0; + for (int i = 0; i < w * h; i++) { + if(Color.alpha(mPixelsARGB[i]) > MIN_VISIBLE_ALPHA) { + sum++; + } + } + float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height()); + boolean transparentImage = percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD; + if (DEBUG) { + Log.d(TAG, "Total # pixel that is different (id="+ mFileId + "):" + percentageDiffPixels + "="+ sum + "/" + mBounds.width() * mBounds.height()); + } + return transparentImage; } /** @@ -79,7 +226,15 @@ public class IconNormalizer { * * @param outBounds optional rect to receive the fraction distance from each edge. */ - public synchronized float getScale(Drawable d, RectF outBounds) { + public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds, + @Nullable Path path, @Nullable boolean[] outMaskShape) { + if (Utilities.isAtLeastO() && d instanceof AdaptiveIconDrawable && + mAdaptiveIconScale != SCALE_NOT_INITIALIZED) { + if (outBounds != null) { + outBounds.set(mBounds); + } + return mAdaptiveIconScale; + } int width = d.getIntrinsicWidth(); int height = d.getIntrinsicHeight(); if (width <= 0 || height <= 0) { @@ -169,20 +324,30 @@ public class IconNormalizer { if (hullByRect < CIRCLE_AREA_BY_RECT) { scaleRequired = MAX_CIRCLE_AREA_FACTOR; } else { - scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect); + scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect); } + mBounds.left = leftX; + mBounds.right = rightX; - if (outBounds != null) { - outBounds.left = ((float) leftX) / width; - outBounds.right = 1 - ((float) rightX) / width; + mBounds.top = topY; + mBounds.bottom = bottomY; - outBounds.top = ((float) topY) / height; - outBounds.bottom = 1 - ((float) bottomY) / height; + if (outBounds != null) { + outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top), + 1 - ((float) mBounds.right) / width, + 1 - ((float) mBounds.bottom) / height); } + if (outMaskShape != null && outMaskShape.length > 0) { + outMaskShape[0] = isShape(path); + } float areaScale = area / (width * height); // Use sqrt of the final ratio as the images is scaled across both width and height. float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1; + if (Utilities.isAtLeastO() && d instanceof AdaptiveIconDrawable && + mAdaptiveIconScale == SCALE_NOT_INITIALIZED) { + mAdaptiveIconScale = scale; + } return scale; } diff --git a/src/com/android/launcher3/graphics/IconShapeOverride.java b/src/com/android/launcher3/graphics/IconShapeOverride.java index 6e4d36642..a0727fb4b 100644 --- a/src/com/android/launcher3/graphics/IconShapeOverride.java +++ b/src/com/android/launcher3/graphics/IconShapeOverride.java @@ -38,7 +38,7 @@ 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; @@ -169,7 +169,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; diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java index 2d987cc4a..f652a5c15 100644 --- a/src/com/android/launcher3/graphics/LauncherIcons.java +++ b/src/com/android/launcher3/graphics/LauncherIcons.java @@ -95,8 +95,29 @@ public class LauncherIcons { */ public static Bitmap createBadgedIconBitmap( Drawable icon, UserHandle user, Context context) { - float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? - 1 : IconNormalizer.getInstance(context).getScale(icon, null); + + IconNormalizer normalizer; + float scale = 1f; + if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) { + normalizer = IconNormalizer.getInstance(context); + if (Utilities.isAtLeastO()) { + boolean[] outShape = new boolean[1]; + AdaptiveIconDrawable dr = (AdaptiveIconDrawable) + context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate(); + dr.setBounds(0, 0, 1, 1); + scale = normalizer.getScale(icon, null, dr.getIconMask(), outShape); + if (FeatureFlags.LEGACY_ICON_TREATMENT && + !outShape[0]){ + Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale); + if (wrappedIcon != icon) { + icon = wrappedIcon; + scale = normalizer.getScale(icon, null, null, null); + } + } + } else { + scale = normalizer.getScale(icon, null, null, null); + } + } Bitmap bitmap = createIconBitmap(icon, context, scale); if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.isAtLeastO() && icon instanceof AdaptiveIconDrawable) { @@ -129,8 +150,29 @@ public class LauncherIcons { */ public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context) { RectF iconBounds = new RectF(); - float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? - 1 : IconNormalizer.getInstance(context).getScale(icon, iconBounds); + IconNormalizer normalizer; + float scale = 1f; + if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) { + normalizer = IconNormalizer.getInstance(context); + if (Utilities.isAtLeastO()) { + boolean[] outShape = new boolean[1]; + AdaptiveIconDrawable dr = (AdaptiveIconDrawable) + context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate(); + dr.setBounds(0, 0, 1, 1); + scale = normalizer.getScale(icon, iconBounds, dr.getIconMask(), outShape); + if (Utilities.isAtLeastO() && FeatureFlags.LEGACY_ICON_TREATMENT && + !outShape[0]) { + Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale); + if (wrappedIcon != icon) { + icon = wrappedIcon; + scale = normalizer.getScale(icon, iconBounds, null, null); + } + } + } else { + scale = normalizer.getScale(icon, iconBounds, null, null); + } + + } scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds)); return createIconBitmap(icon, context, scale); } @@ -180,10 +222,8 @@ public class LauncherIcons { * @param scale the scale to apply before drawing {@param icon} on the canvas */ public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) { - icon = wrapToAdaptiveIconDrawable(context, icon); synchronized (sCanvas) { final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize; - int width = iconBitmapSize; int height = iconBitmapSize; @@ -242,7 +282,7 @@ public class LauncherIcons { * shrink the legacy icon and set it as foreground. Use color drawable as background to * create AdaptiveIconDrawable. */ - static Drawable wrapToAdaptiveIconDrawable(Context context, Drawable drawable) { + static Drawable wrapToAdaptiveIconDrawable(Context context, Drawable drawable, float scale) { if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.isAtLeastO())) { return drawable; } @@ -252,8 +292,10 @@ public class LauncherIcons { if (!clazz.isAssignableFrom(drawable.getClass())) { Drawable iconWrapper = context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate(); - ((FixedScaleDrawable) clazz.getMethod("getForeground").invoke(iconWrapper)) - .setDrawable(drawable); + FixedScaleDrawable fsd = ((FixedScaleDrawable) clazz.getMethod("getForeground") + .invoke(iconWrapper)); + fsd.setDrawable(drawable); + fsd.setScale(scale); return iconWrapper; } 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 07e99c693..7899846d8 100644 --- a/src/com/android/launcher3/logging/UserEventDispatcher.java +++ b/src/com/android/launcher3/logging/UserEventDispatcher.java @@ -29,7 +29,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 +60,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 isInMultiWindowMode) { UserEventDispatcher ued = Utilities.getOverrideObject(UserEventDispatcher.class, diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java index 969605483..10fb5828c 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -33,6 +33,7 @@ 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.Provider; @@ -140,7 +141,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 +149,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 +175,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; + } } } } diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index 0e73ca6d3..be93be4dc 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; @@ -242,7 +240,7 @@ public class BgDataModel { switch (item.itemType) { case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: folders.remove(item.id); - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { for (ItemInfo info : itemsIdMap) { if (info.container == item.id) { // We are deleting a folder which still contains items that diff --git a/src/com/android/launcher3/model/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/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/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 ee7186aa4..0a2ec2acc 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; @@ -31,7 +30,6 @@ 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.LauncherModel.CallbackTask; import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherSettings; @@ -40,10 +38,12 @@ 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.PackageManagerHelper; import java.util.ArrayList; import java.util.Arrays; @@ -94,6 +94,9 @@ 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); } @@ -222,17 +225,14 @@ public class PackageUpdatedTask extends ExtendedModelTask { AppInfo appInfo = addedOrUpdatedApps.get(cn); if (si.isPromise() && mOp == OP_ADD) { - if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) { + 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); diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java index 278669bdb..bae5c73c1 100644 --- a/src/com/android/launcher3/model/SdCardAvailableReceiver.java +++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java @@ -19,7 +19,6 @@ 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.LauncherModel; 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..827675a83 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; @@ -83,7 +83,7 @@ public class WidgetsModel { } setWidgetsAndShortcuts(widgetsAndShortcuts, context, 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 diff --git a/src/com/android/launcher3/pageindicators/CaretDrawable.java b/src/com/android/launcher3/pageindicators/CaretDrawable.java index 0a00e24e9..349b41cb2 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; @@ -46,7 +45,7 @@ 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, android.R.attr.textColorPrimary)); mCaretPaint.setAntiAlias(true); mCaretPaint.setStrokeWidth(strokeWidth); mCaretPaint.setStyle(Paint.Style.STROKE); 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/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java index 0853c13d3..73112986a 100644 --- a/src/com/android/launcher3/popup/PopupItemView.java +++ b/src/com/android/launcher3/popup/PopupItemView.java @@ -35,7 +35,7 @@ import android.widget.FrameLayout; import com.android.launcher3.LogAccelerateInterpolator; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.util.PillRevealOutlineProvider; +import com.android.launcher3.anim.PillRevealOutlineProvider; /** * An abstract {@link FrameLayout} that supports animating an item's content diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java index b0482f8b2..3e4cd0192 100644 --- a/src/com/android/launcher3/provider/ImportDataTask.java +++ b/src/com/android/launcher3/provider/ImportDataTask.java @@ -37,6 +37,7 @@ import com.android.launcher3.DefaultLayoutParser; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherFiles; +import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherSettings.Settings; @@ -46,7 +47,6 @@ import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.GridSizeMigrationTask; import com.android.launcher3.util.LongArrayMap; @@ -112,7 +112,7 @@ public class ImportDataTask { screenOps.add(ContentProviderOperation.newInsert( LauncherSettings.WorkspaceScreens.CONTENT_URI).withValues(v).build()); } - mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY, screenOps); + mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, screenOps); importWorkspaceItems(allScreens.get(0), screenIdMap); GridSizeMigrationTask.markForMigration(mContext, mMaxGridSizeX, mMaxGridSizeY, mHotseatSize); @@ -289,7 +289,7 @@ public class ImportDataTask { } if (insertOperations.size() >= BATCH_INSERT_SIZE) { - mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY, + mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, insertOperations); insertOperations.clear(); } @@ -300,7 +300,7 @@ public class ImportDataTask { throw new Exception("Insufficient data"); } if (!insertOperations.isEmpty()) { - mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY, + mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, insertOperations); insertOperations.clear(); } @@ -319,7 +319,7 @@ public class ImportDataTask { mHotseatSize = (int) hotseatItems.keyAt(hotseatItems.size() - 1) + 1; if (!insertOperations.isEmpty()) { - mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY, + mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, insertOperations); } } diff --git a/src/com/android/launcher3/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/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/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java index aedca8db4..12f3b846a 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; @@ -12,7 +11,6 @@ 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; 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/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/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/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. |